Webpack系列-第一篇基础杂记
Webpack系列-第二篇插件机制杂记
Webpack系列-第三篇流程杂记css
公司的前端项目基本都是用Webpack来作工程化的,而Webpack虽然只是一个工具,但内部涉及到很是多的知识,以前一直靠CV来解决问题,之知其然不知其因此然,但愿此次能整理一下相关的知识点。前端
这是webpack官方的首页图vue
本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序须要的每一个模块,而后将全部这些模块打包成一个或多个 bundle。node
那么打个比方就是咱们搭建一个项目比如搭建一个房子,咱们把所须要的材料(js文件、图片等)交给webpack,最后webpack会帮咱们作好一切,并把房子(即bundle)输出。react
webpack中有几个概念须要记住webpack
入口起点(entry point)便是webpack经过该起点找到本次项目所直接或间接依赖的资源(模块、库等),并对其进行处理,最后输出到bundle中。入口文件由用户自定义,能够是一个或者多个,每个entry最后对应一个bundle。git
经过配置output属性能够告诉webpack将bundle命名并输出到对应的位置。es6
webpack核心,webpack自己只能识别js文件,对于非js文件,即须要loader转换为js文件。换句话说,,Loader就是资源转换器。因为在webpack里,全部的资源都是模块,不一样资源都最终转化成js去处理。针对不一样形式的资源采用不一样的Loader去编译,这就是Loader的意义。github
webpack核心,loader处理非js文件,那么插件能够有更普遍的用途。整个webpack其实就是各种的插件造成的,插件的范围包括,从打包优化和压缩,一直到从新定义环境中的变量。插件接口功能极其强大,能够用来处理各类各样的任务。web
被entry所依赖的额外的代码块,一样能够包含一个或者多个文件。chunk也就是一个个的js文件,在异步加载中用处很大。chunk实际上就是webpack打包后的产物,若是你不想最后生成一个包含全部的bundle,那么能够生成一个个chunk,并经过按需加载引入。同时它还能经过插件提取公共依赖生成公共chunk,避免多个bundle中有多个相同的依赖代码。
webpack的相关配置语法官方文档比较详细,这里就不赘述了。
指南
配置
url-loader 能够在文件大小(单位 byte)低于指定的限制,将文件转换为DataURL,这在实际开发中很是有效,可以减小请求数,在vue-cli和create-react-app中也都能看到对这个loader的使用。
// "url" loader works just like "file" loader but it also embeds
// assets smaller than specified size as data URLs to avoid requests.
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve('url-loader'),
options: {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]',
},
},
复制代码
image-webpack-loader 这是一个能够经过设置质量参数来压缩图片的插件,但我的以为在实际开发中并不会常用,图片通常是UI提供,通常来讲,他们是不会赞成图片的质量有问题。
以这种方式加载资源,你能够以更直观的方式将模块和资源组合在一块儿。无需依赖于含有所有资源的 /assets 目录,而是将资源与代码组合在一块儿。例如,相似这样的结构会很是有用
- |- /assets
+ |– /components
+ | |– /my-component
+ | | |– index.jsx
+ | | |– index.css
+ | | |– icon.svg
+ | | |– img.png
复制代码
固然,这种选择见仁见智
前端中的tree-shaking就是将一些无关的代码删掉不打包。在Webpack项目中,咱们一般会引用不少文件,但实际上咱们只引用了其中的某些模块,但却须要引入整个文件进行打包,会致使咱们的打包结果变得很大,经过tree-shaking将没有使用的模块摇掉,这样来达到删除无用代码的目的。
概括起来就是
1.ES6的模块引入是静态分析的,故而能够在编译时正确判断到底加载了什么代码。
2.分析程序流,判断哪些变量未被使用、引用,进而删除此代码
概括起来就是
由于Babel的转译,使得引用包的代码有了反作用,而反作用会致使Tree-Shaking失效。
Webpack 4 默认启用了 Tree Shaking。对反作用进行了消除,如下是我在4.19.1的实验
index.js
import { cube } from './math.js'
console.log(cube(5))
复制代码
math.js
// 不打包square
export class square {
constructor() {
console.log('square')
}
}
export class cube {
constructor(x) {
return x * x * x
}
}
复制代码
// babel编译后 同不打包
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.cube = cube;
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var square = exports.square = function square() {
_classCallCheck(this, square);
console.log('square');
};
function cube(x) {
console.log('cube');
return x * x * x;
}
复制代码
// 不打包
export function square(x) {
console.log('square')
return x.a
}
export function cube (x) {
return x * x * x
}
复制代码
// wow 被打包
export function square() {
console.log('square')
return x.a
}
square({a: 1})
export function cube () {
return x * x * x
}
复制代码
简单说,Source map就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每个位置,所对应的转换前的位置。
有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码。这无疑给开发者带来了很大方便。
webpack中的devtool配置项能够设置sourcemap,能够参考官方文档然而,devtool的许多选项都讲的不是很清楚,这里推荐该文章,讲的比较详细
要注意,避免在生产中使用 inline-* 和 eval-*,由于它们能够增长 bundle 大小,并下降总体性能。
热替换这一块目前大多数都是用的webpack-dev-middleware插件配合服务器使用的,而官方提供的watch模式反而比较少用,固然,webpack-dev-middleware的底层监听watch mode,至于为何不直接使用watch模式,则是webpack-dev-middleware快速编译,走内存;只依赖webpack的watch mode来监听文件变动,自动打包,每次变动,都将新文件打包到本地,就会很慢。
webpack.DefinePlugin 定义环境变量process.env,这在实际开发中比较经常使用,参考create-react-app中的代码以下:
// Makes some environment variables available to the JS code, for example:
// if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`.
new webpack.DefinePlugin(env.stringified),
复制代码
不过,要注意不能在config中使用,由于
process.env.NODE_ENV === 'production' ? '[name].[hash].bundle.js' : '[name].bundle.js'
复制代码
NODE_ENV
is set in the compiled code, not in the webpack.config.js file. You should not use enviroment variables in your configuration. Pass options via--env.option abc
and export a function from the webpack.config.js.
大体意思就是NODE_ENV是设置在compiled里面,而不是config文件里。
ExtractTextWebpackPlugin,将css抽取成单独文件,能够经过这种方式配合后端对CSS文件进行缓存。
webpack4的代码分割插件。
webpack4中支持了零配置的特性,同时对块打包也作了优化,CommonsChunkPlugin已经被移除了,如今是使用optimization.splitChunks代替。
SplitChunksPlugin的配置有几个须要比较关注一下
chunks: async | initial | all
cacheGroups
使用cacheGroups能够自定义配置打包块。
更多详细内容参考该文章
则是利用动态引入的文件打包成另外一个包,并懒加载它。其与SplitChunksPlugin的cacheGroups区别:
+ import _ from 'lodash';
+
+ function component() {
var element = document.createElement('div');
+ var button = document.createElement('button');
+ var br = document.createElement('br');
+ button.innerHTML = 'Click me and look at the console!';
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+ element.appendChild(br);
+ element.appendChild(button);
+
+ // Note that because a network request is involved, some indication
+ // of loading would need to be shown in a production-level site/app.
+ button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => {
+ var print = module.default;
+
+ print();
+ });
return element;
}
+ document.body.appendChild(component());
复制代码
注意当调用 ES6 模块的 import() 方法(引入模块)时,必须指向模块的 .default 值,由于它才是 promise 被处理后返回的实际的 module 对象。
由于webpack会把运行时代码放到最后的一个bundle中, 因此即便咱们修改了其余文件的代码,最后的一个bundle的hash也会改变,runtimeChunk是把运行时代码单独提取出来的配置。这样就有利于咱们和后端配合缓存文件。
配置项
有时候咱们只是添加了个文件print.js, 并在index引入
import Print from './print'
复制代码
打包的时候,指望只有runtime和main两个bundle的hash发生改变,可是一般全部bundle都发生了变化,由于每一个 module.id 会基于默认的解析顺序(resolve order)进行增量。也就是说,当解析顺序发生变化,ID 也会随之改变。
可使用两个插件来解决这个问题。第一个插件是 NamedModulesPlugin,将使用模块的路径,而不是数字标识符。虽然此插件有助于在开发过程当中输出结果的可读性,然而执行时间会长一些。第二个选择是使用 HashedModuleIdsPlugin。
参考文章
经过ProvidePlugin处理全局变量
其余更细粒度的处理
首先了解一下polyfills, 虽然在webpack中可以使用es6\es7等的API,但并不表明编译器支持这些API,因此一般咱们会用polyfills来自定义一个API。
那么在webpack中,通常是使用babel-polyfill VS babel-runtime VS babel-preset-env等来支持这些API,而这三种怎么选择也是一个问题。
在真正进入主题以前,咱们先看一个preset-env的配置项,同时也是package.json中的一个配置项browserslist
{
"browserslist": [
"last 1 version",
"> 1%",
"maintained node versions",
"not dead"
]
}
复制代码
根据这个配置,preset-env或者postcss等会根据你的参数支持不一样的polyfills,具体的参数配置参考该文章
另外推荐一个网站,能够看各类浏览器的使用状况。
- babel-polyfill 只须要引入一次,但会重写一些原生的已支持的方法,并且体积很大。
- transform-runtime 是利用 plugin 自动识别并替换代码中的新特性,你不须要再引入,只须要装好 babel-runtime 和 配好 plugin 就能够了。好处是按需替换,检测到你须要哪一个,就引入哪一个 polyfill,值得注意的是,instance 上新添加的一些方法,babel-plugin-transform-runtime 是没有作处理的,好比 数组的 includes, filter, fill 等
- babel-preset-env 根据当前的运行环境,自动肯定你须要的 plugins 和 polyfills。经过各个 es标准 feature 在不一样浏览器以及 node 版本的支持状况,再去维护一个 feature 跟 plugins 之间的映射关系,最终肯定须要的 plugins。
参考文章
平常咱们引用的Npm包都是编译好的,这样带来的方便的同时也暴露了一些问题。
代码冗余:通常来讲,这些 NPM 包也是基于 ES2015+ 开发的,每一个包都须要通过 babel 编译发布后才能被主应用使用,而这个编译过程每每会附加不少“编译代码”;每一个包都会有一些相同的编译代码,这就形成大量代码的冗余,而且这部分冗余代码是不能经过 Tree Shaking 等技术去除掉的。
非必要的依赖:考虑到组件库的场景,一般咱们为了方便一股脑引入了全部组件;但实际状况下对于一个应用而言可能只是用到了部分组件,此时若是所有引入,也会形成代码冗余。
因此咱们本身的公司组件能够采用后编译的形式,即发布的是未经编译的npm包,在项目构建时才编译,咱们公司采用的也是这种作法,由于咱们的包都在一个目录下,因此不用考虑递归编译的问题。
更多的详细请直接参考该文章
这个比较简单,直接看代码或者官方文档便可
webpack --env.NODE_ENV=local --env.production --progress
复制代码
Webpack自己并不难于理解,难的是各类各样的配置和周围生态带来的复杂,然而也是这种复杂给咱们带来了极高的便利性,理解这些有助于在搭建项目更好的优化。后面会继续写出两篇总结,分别是webpack的内部原理流程和webpack的插件开发原理。