webpack 是一个模块打包器。webpack 的主要目标是将 javaScript
文件打包在一块儿,打包后的文件用于在浏览器中使用,但它也可以胜任转换(transform)、打包(bundle)或包裹(package)任何资源(resource or asset)。css
随着 webpack 不断地发展,webpack 配置变得愈来愈简单,构建速度也愈来愈快,官方文档上说 webpack4 比 webpack3 构建速度快了 98%,这还不只如此,官方标识在 webpack5 中,会使用多进程构建,进一步优化构建速度。html
本文已同步到我的博客,欢迎 start,谢谢。前端
入口是 webpack 构建开始的地方,经过入口文件,webpack 能够找到入口文件所依赖的文件,并逐步递归,找出全部依赖的文件。vue
module.exports = { entry: "./path/to/my/entry/file.js" };
output 属性告诉 webpack 在哪里输出它所建立的 bundles,以及如何命名这些文件。java
const path = require("path"); module.exports = { entry: "./path/to/my/entry/file.js", output: { path: path.resolve(__dirname, "dist"), filename: "my-first-webpack.bundle.js" } };
webpack 自身只支持 JavaScript。而 loader 可以让 webpack 处理那些非 JavaScript 文件,而且先将它们转换为有效的模块,而后添加到依赖图中,这样就能够提供给应用程序使用。node
const path = require("path"); module.exports = { output: { filename: "my-first-webpack.bundle.js" }, module: { rules: [ { // 根据后缀名匹配须要处理的文件 test: /\.txt$/, // 使用对应的loader处理文件 use: "raw-loader" } ] } };
loader 其实就是一个 function,接收一个参数 source,就是当前的文件内容,而后稍加处理,就能够 return 出一个新的文件内容。react
const loaderUtils = require("loader-utils"); module.exports = function(source) { // 获取loader中传递的配置信息 const options = loaderUtils.getOptions(this); // 返回处理后的内容 this.callback(null, "/ *增长一个注释 */" + source); // 也能够直接return // return "/ *增长一个注释 */" + source; };
插件其实就是一个类,经过监听 webpack 执行流程上的钩子函数,能够更精密地控制 webpack 的输出,包括:打包优化、资源管理和注入环境变量。webpack
const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { module: { rules: [{ test: /\.txt$/, use: "raw-loader" }] }, plugins: [new HtmlWebpackPlugin({ template: "./src/index.html" })] };
咱们能够利用 webpack 提供的钩子函数,编写自定义插件,至关于监听 webpack 的事件,作出对应的响应,webpack 是经过Tapable进行事件流管理的。git
class APlugin { // apply方法,会在new plugin后被webpack自动执行。 apply(compiler) { // 能够在任意的钩子函数中去触发自定义事件,也能够监听其余事件:compiler.hooks.xxxx compiler.hooks.compilation.tap("APlugin", compilation => { compilation.hooks.afterOptimizeChunkAssets.tap("APlugin", chunks => { // 这里只是简单的打印了chunks,你若是有更多的想法,均可以在这里实现。 console.log("打印chunks:", chunks); }); }); } }
在 webpack4 发布后,相比 webpack3 的构建进行了高效地优化,速度提升了 98%,一些常规优化 webpack 都已经帮咱们作了,使得 webpack 变得愈来愈简单,甚至能够达到零配置,可是对于零配置而言,不能知足所有需求,因此仍是建议进行手动配置。github
最懒人的写法,在 webpack 配置项中 mode = production ,webpack 就帮咱们把经常使用的配置都配好了,并且彻底能够胜任大部分需求。
module.exports = { mode: "production" };
使用该配置后,webpack 会将 process.env.NODE_ENV 的值设为 production。
而且还会帮咱们配置好如下插件:
若是不使用 plugin,webpack 会把全部文件都打包在一个 js 文件中,这每每会使得文件很大,加载时间会变得很长,咱们能够配置 optimization.splitChunks 来设置拆分文件规则。
这是 webpack 默认的配置,也能够根据本身需求作对应修改。
module.exports = { optimization: { splitChunks: { chunks: "async", // 参数多是:all,async和initial,这里表示拆分异步模块。 minSize: 30000, // 若是模块的大小大于30kb,才会被拆分 minChunks: 1, maxAsyncRequests: 5, // 按需加载时最大的请求数,意思就是说,若是拆得很小,就会超过这个值,限制拆分的数量。 maxInitialRequests: 3, // 入口处的最大请求数 automaticNameDelimiter: "~", // webpack将使用块的名称和名称生成名称(例如vendors~main.js) name: true, // 拆分块的名称 cacheGroups: { // 缓存splitchunks vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 }, default: { minChunks: 2, // 一个模块至少出现2次引用时,才会被拆分 priority: -20, reuseExistingChunk: true } } } } };
纵观 webpack 构建流程,咱们能够发现整个构建过程主要花费时间的部分也就是递归遍历各个 entry 而后寻找依赖逐个编译的过程,每次递归都须要经历 String->AST->String 的流程,通过 loader 还须要处理一些字符串或者执行一些 JS 脚本,介于 node.js 单线程的壁垒,webpack 构建慢一直成为它饱受诟病的缘由。
// @file: webpack.config.js var HappyPack = require("happypack"); var happyThreadPool = HappyPack.ThreadPool({ size: 5 }); module.exports = { // ... plugins: [ new HappyPack({ id: "jsx", threadPool: happyThreadPool, loaders: ["babel-loader"] }), new HappyPack({ id: "styles", threadPool: happyThreadPool, loaders: ["style-loader", "css-loader", "less-loader"] }) ] }; exports.module.rules = [ { test: /\.js$/, use: "happypack/loader?id=jsx" }, { test: /\.less$/, use: "happypack/loader?id=styles" } ];
Happypack 其实是使用了 node processes 执行多线程构建,可让多个 loader 并行执行,从而加快构建。
DllPlugin:用于打包单独的动态连接库文件。
DllReferencePlugin:用于在主要的配置文件中引入 DllPlugin 插件打包好的动态连接库文件。
这里须要建 2 个配置文件,先执行 webpack.dll.config.js,生成 mainfest,而后再执行 webpack.config.js 打包文件,能够看到,构建速度有了很是大的提高。
动态连接库配置:
// webpack.dll.config.js // 这里配置DllPlugin,生成mainifest module.exports = { entry:{ // 将react相关,放入一个单独的动态连接库中 react:['react','react-dom'] }, output:{ filename:'[name].dll.js' }, plugins:[ new webpack.DllPlugin({ name: '_dll_[name]', path: path.join(__dirname, '[name].manifest.json'), ); ] };
使用打包后的动态连接库:
// webpack.config.js // 这里配置DllPlugin,生成mainifest module.exports = { plugins:[ new webpack.DllReferencePlugin({ manifest: require('./react.manifest.json') }); ] };
一、在处理 loader
时,配置 include
,缩小 loader
检查范围。
二、使用 alias
能够更快地找到对应文件。
三、若是在 require
模块时不写后缀名,默认 webpack 会尝试.js
,.json
等后缀名匹配,extensions
配置,让 webpack 少作一点后缀匹配。
四、thread-loader
能够将很是消耗资源的 loaders 转存到 worker pool 中。
五、使用 cache-loader
启用持久化缓存。使用 package.json 中的 "postinstall" 清除缓存目录。
一、选择合理额 Devtool 在大多数状况下,cheap-module-eval-source-map
是最好的选择。
二、开发阶段通常不须要进行压缩合并,提权单独文件等操做。
三、webpack 会在输出文件中生成路径信息。然而在打包数千个模块的项目中,会致使形成垃圾回收性能压力。在 options.output.pathinfo
设置中关闭.
四、在开发阶段,能够直接引用 cdn
上的库文件,使用 externals
配置全局对象,避免打包。
一、静态资源上 cdn。
二、使用 tree shaking
,只打包用到的模块,删除没有用到的模块。
三、配置 scope hoisting
做用域提高,将多个 IIFE 放在一个 IIFE 中。
相关的代码以下:
module.exports = { output: { // 静态资源上cdn publicPath: "//xxx/cdn.com", // 不生成「所包含模块信息」的相关注释 pathinfo: false }, module: { rules: [ { test: /\.txt$/, use: "raw-loader", // 缩小loader检查范围 include: path.join(__dirname, "src") } ] }, plugins: [ // 开启scope hoisting new ModuleConcatenationPlugin() ], resolve: { // 使用别名,加快搜索 alias: { "~": path.resolve(__dirname, "../src") }, // 配置用到的后缀名,方便webpack查找 extensions: ["js", "css"] }, // 开发阶段引用cdn上文件,能够避免打包库文件 externals: { vue: "Vue", "element-ui": "ELEMENT" } };
webpack 在运行时大体分为这几个阶段:
一、读取 webpack.config.js 配置文件,生成 compiler 实例,并把 compiler 实例注入 plugin 中的 apply 方法中。
二、读取配置的 Entries
,递归遍历全部的入口文件。
三、对入口文件进行编译,开始 compilation
过程,使用 loader
对文件内容编译,再将编译好的文件内容解析成 AST
静态语法树。
四、递归依赖的模块,重复第 3 步,生成 AST
语法树,在 AST
语法树中能够分析到模块之间的依赖关系,对应作出优化。
五、将全部模块中的 require 语法替换成__webpack_require__
来模拟模块化操做。
六、最后把全部的模块打包进一个自执行函数(IIFE)中。
这张图画的很好,把webpack的流程画的很细致。
图片是参考文章Webpack揭秘——走向高阶前端的必经之路里的,若有侵权,请联系我,立刻删除。
::: tip 参考资料
webpack 官网