Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

实用webpack插件之ProvidePlugin #214

Open
FrankKai opened this issue Apr 29, 2020 · 4 comments
Open

实用webpack插件之ProvidePlugin #214

FrankKai opened this issue Apr 29, 2020 · 4 comments

Comments

@FrankKai
Copy link
Owner

FrankKai commented Apr 29, 2020

现代化前端的全局引入是一个很有趣的东西。
先来看下以下几个场景:

  • 在webpack中,我们可以在resolve的alias中定义一个层级较高的目录为一个自定义变量。例如resolve: { alias: { '@': path.join(__dirname, '..', 'src') }}
  • 在webpack中,我们也可以通过DefinePlugin将配置文件按照环境变量进行区分,高效的完成配置文件的按环境引入,无论是开发构建还是生产构建,都十分有用。
  • 在vue中,我们可以将一个常用的方法或者库定义在Vue.ptototye上,可以通过直写属性,也可以通过vue中的plugin install上去。例如Vue.prototype.$_ = lodash,在应用了vue的应用上下文中,可以通过this.$_获得对lodash的引用。
  • 在vue中,还有mixins,inject以及vuex等等这些全局绑定或者叫混入、注入方式的全局引入的实现。

来思考一个问题:

如果我们再Vue.prototype上绑定了太多,太大的第三方库,会不会导致root vue过大?
答案是肯定的。

有没有办法解决这个问题?
你可能会说,我不用this.xxx。用到的vue单文件组件直接import或者require就好了。

如果数以百计,数以千计甚至是数以万计的.vue文件中用到了呢?一直引入吗?
可以一直引入。但是会造成不必要的工作量。

有没有更加优雅的解决办法?

再来思考一个问题:

如果我要在一个webpack打包覆盖的地方的xxx.js文件中用到lodash,该怎么做?
通常来讲,我们会直接`import _ from' lodash'`或者`const _ = require('lodash')`。

如果和.vue一样,有很多很多js文件需要引入呢?一直引入吗?
可以一直引入。同样会造成不必要的工作量。

有没有更加优雅的实现方式?

看一张一直引入moment,引了99次的图先来感受一下:


虽然我的项目中是在优化moment的引入,但是为了直观明了,我将以引入lodash为例。

  • 使用ProvidePlugin的三种方式
  • 为何一直引入造成不必要工作量
  • 使用ProvidePlugin引入实践
    • webpack的plugins中增加$_的配置
    • eslint的globals增加$_的配置
    • 在Vue中如何使用$_
    • 在Vue的template中使用的注意事项
      • 为什么这个是最推荐的呢?
      • 那为什么不挂载到data上呢?
  • 思考
    • 使用ProvidePlugin后会比一直引入减小打包体积吗?
    • 使用ProvidePlugin有哪些注意事项?
    • 注入的ProvidePlugin是一个什么东西?
@FrankKai
Copy link
Owner Author

使用ProvidePlugin的三种方式

// 语法
new webpack.ProvidePlugin({
  identifier: 'module1',
  // identifier: ['module1', 'property1'],
});
  • module.exports
    • 直接引入
    • 引入某个函数
  • export default

module.exports

直接引入
new webpack.ProvidePlugin({
  $_: 'lodash',
});
引入某个函数
new webpack.ProvidePlugin({
  $_uniqBy: ['lodash','uniqBy']
});

export default

new webpack.ProvidePlugin({
  Vue: ['vue/dist/vue.esm.js', 'default']
});

@FrankKai
Copy link
Owner Author

为何一直引入造成不必要工作量

加入我们有a~z,a.js到z.js总结26个js文件,每个文件都需要引入lodash。

// a.js
import $_ from 'lodash';
// b.js
import $_ from 'lodash';
// c.js
import $_ from 'lodash';
// d.js
import $_ from 'lodash';
// e.js
import $_ from 'lodash';
// f.js
import $_ from 'lodash';
...
// z.js
import $_ from 'lodash';

这样做有以下几个弊端

  • 要乖乖引入26次
  • import进来之后的自定义名称可能会不统一,导致全局搜索困难

比如说下面这种场景,对于代码可读性是很不好的。

// a.js
import $_ from 'lodash';
// b.js
import _ from 'lodash';

@FrankKai
Copy link
Owner Author

FrankKai commented Apr 29, 2020

使用ProvidePlugin引入实践

  • webpack的plugins中增加$_的配置
  • eslint的globals增加$_的配置
  • 在Vue中如何使用$_
  • 在Vue的template中使用的注意事项

webpack的plugins中增加$_的配置

// webpack.base.config.js
plugins: [
    new webpack.ProvidePlugin({
      $_: 'lodash',
    }),
],

eslint的globals增加$_的配置

// .eslintrc.js
globals: {
    $_: 'readonly', // 或者true
},

配置为readonly是因为我们不会改写lodash,仅仅是调用其方法。

在Vue中如何使用$_

假设在a.js中。
删除单独的lodash引入 _ :import _ from 'lodash'
script中直接使用$_ :$_.uniqBy(...)
template的使用事项可以看下文。

在Vue的template中使用的注意事项

ProvidePlugin注入的全局变量,在script中是完全没有问题的,但是在template中使用时会有一些小问题。

例如下面这样:

<p>{{$_(...)}</p>
data() {
    return {
         $_,
    }
}

遇到这种情况是什么原因呢?
Vue的模板语法中,不支持直接对以$或者_开头的自定义data属性,目的是避免与Vue的内部冲突。

[Vue warn]: 
Property "$_" must be accessed with "$data.$_".
Because properties starting with "$" or "_" are not proxied in the Vue instance to prevent conflicts with Vue internals

有以下几种方式解决这个问题:

  • 通过$data.$_访问
  • data中重命名后绑定
  • methods中绑定(最推荐)
通过$data.$_访问
<p>{{$data.$_(...)}</p>
data中重命名后绑定
<p>{{globalLodash(...)}</p>
data() {
    return {
          globalLodash: $_,
    }
}
methods中绑定(最推荐)
<p>{{$_(...)}</p>
methods: {
    $_
}
为什么这个是最推荐的呢?

这是因为ProvidePlugin最终返回给我们的,是一个hooks函数。

hooks () {
     return hookCallback.apply(null, arguments);
}

既然是一个函数,那么它其实就是一个method。
由于需要在vue的template中使用,所以需要将其挂载到vue实例上。
因此直接在methods中绑定,挂载到vue示例。

那为什么不挂载到data上呢?

避免额外的无用的开销。
这是因为data是用来定义一些响应式的数据的,我们的$_只是一个工具函数,不会有双向绑定的事情发生在它身上,因此也不需要定义在data中,vue不用为其定义单独的watcher,dep,getter,setter等等。

@FrankKai
Copy link
Owner Author

FrankKai commented Apr 29, 2020

思考

注入的ProvidePlugin是一个什么东西?

是一个hooks函数。

hooks () {
     return hookCallback.apply(null, arguments);
}

使用ProvidePlugin后会比一直引入减小打包体积吗?

不会。
反而会略微增大一些,0.0X KB。
这是我自己对比使用ProvidePlugin前使用ProvidePlugin后打包文件体积大小得出的结论。

使用ProvidePlugin有哪些注意事项?

这些注意事项其实主要是为了增强代码可读性和可维护性。

  • 尽量定义出唯一性高的全局变量,例如$_,$moment
  • 同一个前端小组的成员都采用全局变量的方式引入
  • 最好是能维护一个全局变量的文档,在新人入职时特殊强调

看到这里,文章开头Vue.prototype.xxx和import和require重复引入的问题”有没有更加优雅的实现方式?“就迎刃而解啦。

快到你的项目中试试ProvidePlugin吧~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant