Webpack 核心概念
Webpack 核心概念
webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
module
:不同文件类型的模块。Webpack 就是用来对模块进行打包的工具,这些模块各种各样,比如:js 模块、css 模块、sass 模块、vue 模块等等不同文件类型的模块。这些文件都会被 loader 转换为有效的模块,然后被应用所使用并且加入到依赖关系图中。chunk
:数据块。- 非初始化的数据块。例如在打包时,对于一些动态导入的异步代码,Webpack 分割出共用的代码,可以是写的代码模块,也可以是第三方库,这些被分割的代码文件就可以理解为
chunk
。 - 初始化的的数据块。入口文件处 (entry point) 的各种文件或者说模块依赖,就是
chunk
,它们最终会被捆在一起打包成一个输出文件,输出文件可以理解为bundle
,当然它其实也是chunk
。
- 非初始化的数据块。例如在打包时,对于一些动态导入的异步代码,Webpack 分割出共用的代码,可以是写的代码模块,也可以是第三方库,这些被分割的代码文件就可以理解为
bundle
:包含一个或多个chunk
,是源码经过 webpack 处理后的最终版本
context
context 即项目打包的相对路径上下文,必须是一个绝对路径,用于从配置中解析入口 起点(entry point) 和 loader。默认使用当前目录。
entry(入口)
入口起点(entry point) 指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。默认值为 ./src
。
module.exports = {
// ========== 单个入口 ==========
entry: {
index: 'index.js',
},
// ========== 多个入口 ==========
// entry: {
// index: ['index.js', 'app.js'],
// vendor: 'vendor.js',
// },
};
output(输出)
output 属性 告诉 webpack 在哪里输出它所创建的 bundles
,以及如何命名这些文件,默认值为 ./dist
。
const path = require('path');
module.exports = {
// ========== 单个出口 ==========
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'), // 输出路径
filename: 'index.min.js', // 输出文件名
},
// ========== 多个出口 ==========
// entry: {
// index: 'index.js',
// vendor: 'vendor.js',
// },
// output: {
// path: path.resolve(__dirname, 'dist'),
// filename: '[name].min.[hash:5]js',
// },
};
常用配置:
output.chunkFilename
:决定了非入口(non-entry) chunk 文件的名称。output.filename
:决定了每个输出 bundle 的名称。这些 bundle 将写入到output.path
选项指定的目录下。该项不会影响 按需加载 chunk 的输出文件。对于这些文件,使用output.chunkFilename
选项来控制输出。通过 loader 创建的文件也不受影响。可以使用以下占位符:[hash]
:模块标识符(module identifier)的 hash。可以使用[hash:16]
(默认为20)来指定,也可通过指定output.hashDigestLength
在全局配置长度[chunkhash]
:chunk 内容的 hash。hash 长度同[hash]
上配置[name]
:模块名称[id]
:模块标识符(module identifier)[query]
:模块的 query,例如,文件名?
后面的字符串
output.path
:输出路径。output.publicPath
:指定在浏览器中所引用的 此输出目录对应的公开 URL。output.library
:指定库的名称,库的名称支持占位符和普通字符串。output.libraryTarge
:取值范围为:var
、assign
、this
、window
、global
、commonjs
、commonjs2
、commonjs-module
、amd
、umd
、umd2
、jsonp
,默认是var
。// 【 libraryTarge: var 】 // 默认值。当 library 加载完成,入口起点的返回值将分配给一个变量 { output: { library: 'myLib', filename: 'var.js', libraryTarget: 'var' } } // output var myLib = (function(modules) {})({ './src/index.js': function(module, exports) {} }); // =============================================== // 【 libraryTarge: assign 】 // 将产生一个隐含的全局变量,可能会潜在地重新分配到全局中已存在的值(谨慎使用) { output: { library: 'myLib', filename: 'assign.js', libraryTarget: 'assign' } } // output: 少了个 var myLib = (function(modules) {})({ './src/index.js': function(module, exports) {} }); // =============================================== // 【 libraryTarge: this 】 // 入口起点的返回值将分配给 this 的一个属性(此名称由 output.library 定义)下 { output: { library: 'myLib', filename: 'this.js', libraryTarget: 'this' } } // output this["myLib"] = (function(modules) {})({ './src/index.js': function(module, exports) {} }); // =============================================== // 【 libraryTarge: window 】 // 入口起点的返回值将使用 output.library 中定义的值,分配给 window 对象的这个属性下 { output: { library: 'myLib', filename: 'window.js', libraryTarget: 'window' } } // output window["myLib"] = (function(modules) {})({ './src/index.js': function(module, exports) {} }); // =============================================== // 【 libraryTarge: global 】 // 入口起点的返回值将使用 output.library 中定义的值,分配给 global 对象的这个属性下 { output: { library: 'myLib', filename: 'global.js', libraryTarget: 'global' } } // output:注意 target=node 的时候才是 global,默认 target=web下 global 为 window window["myLib"] = (function(modules) {})({ './src/index.js': function(module, exports) {} }); // =============================================== // 【 libraryTarge: commonjs 】 // 入口起点的返回值将使用 output.library 中定义的值,分配给 exports 对象 { output: { library: 'myLib', filename: 'commonjs.js', libraryTarget: 'commonjs' } } // output exports["myLib"] = (function(modules) {})({ './src/index.js': function(module, exports) {} }); // =============================================== // 【 libraryTarge: amd 】 // 将你的 library 暴露为 AMD 模块 { output: { library: 'myLib', filename: 'amd.js', libraryTarget: 'amd' } } // output define('myLib', [], function() { return (function(modules) {})({ './src/index.js': function(module, exports) {} }); }); // =============================================== // 【 libraryTarge: umd 】 // 将 library 暴露为所有的模块定义下都可运行的方式。它将在 CommonJS, AMD 环境下运行,或将模块导出到 global 下的变量 { output: { library: 'myLib', filename: 'umd.js', libraryTarget: 'umd' } } // output (function webpackUniversalModuleDefinition(root, factory) { if (typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if (typeof define === 'function' && define.amd) define([], factory); else if (typeof exports === 'object') exports['myLib'] = factory(); else root['myLib'] = factory(); })(window, function() { return (function(modules) {})({ './src/index.js': function(module, exports) {} }); }); // =============================================== // 【 libraryTarge: commonjs2 】 // 入口起点的返回值将分配给 module.exports 对象。这个名称也意味着模块用于 CommonJS 环境 { output: { library: 'myLib', filename: 'commonjs2.js', libraryTarget: 'commonjs2' } } // output module.exports = (function(modules) {})({ './src/index.js': function(module, exports) {} }); // =============================================== // 【 libraryTarge: umd2 】 { output: { library: 'myLib', filename: 'umd2.js', libraryTarget: 'umd2' } } // output (function webpackUniversalModuleDefinition(root, factory) { if (typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if (typeof define === 'function' && define.amd) define([], factory); else if (typeof exports === 'object') exports['myLib'] = factory(); else root['myLib'] = factory(); })(window, function() { return (function(modules) {})({ './src/index.js': function(module, exports) { } }); }); // =============================================== // 【 libraryTarge: commonjs-module 】 { output: { library: 'myLib', filename: 'commonjs-module.js', libraryTarget: 'commonjs-module' } } // output module.exports = (function(modules) {})({ './src/index.js': function(module, exports) {} }); // =============================================== // 【 libraryTarge: jsonp 】 // 将把入口起点的返回值,包裹到一个 jsonp 包装容器中 { output: { library: 'myLib', filename: 'jsonp.js', libraryTarget: 'jsonp' } } // output myLib((function(modules) {})({ './src/index.js': function(module, exports) {} }));
resolve(解析)
resolve 帮助 Webpack 查找依赖模块的,通过 resolve
的配置,可以帮助 Webpack 快速查找依赖,也可以替换对应的依赖(比如开发环境用 dev 版本的 lib 等)。
常用配置:
resolve.extensions
:帮助 Webpack 解析扩展名的配置,默认值:['.wasm', '.mjs', '.js', '.json']
resolve.alias
:设置别名。通过设置 alias 可以帮助 webpack 更快查找模块依赖。module.exports = { resolve: { alias: { src: path.resolve(__dirname, 'src'), '@lib': path.resolve(__dirname, 'src/lib'), // 在名称末尾添加 $ 符号来缩小范围,只命中以关键字结尾的导入语句,可以做精准匹配 // eg: import react from 'react' // 精确匹配,所以 react.min.js 被解析和导入 // eg: import file from 'react/file.js'; // 非精确匹配,触发普通解析 react$: '/path/to/react.min.js' }, }, };
resolve.mainFields
:当从 npm 包中导入模块时(例如,import * as D3 from "d3"
),此选项将决定在 package.json 中使用哪个字段导入模块。根据 webpack 配置中指定的target
(构建目标)不同,默认值也会有所不同。当 target 属性设置为 webworker, web 或者没有指定,默认值为:mainFields: ["browser", "module", "main"]
// D3 的 package.json 含有这些字段: { ... main: 'build/d3.Node.js', browser: 'build/d3.js', module: 'index', ... }
module(模块)
在 webpack 解析模块的同时,不同的模块需要使用不同类型的模块处理器来处理,这部分的设置就在module配置中。module 有两个配置:module.noParse
和 module.rules
module.noParse
:可以让 Webpack 忽略对部分没采用模块化的文件的递归解析和处理,可以提高构建性能module.exports = { module: { // 使用正则表达式 noParse: /jquery|lodash/ // 使用函数,从 Webpack 3.0.0 开始支持 noParse: (content) => { // content 代表一个模块的文件路径 // 返回 true or false return /jquery|lodash/.test(content); }, }, };
module.rules
:在处理模块时,将符合规则条件的模块,提交给对应的处理器来处理,通常用来配置 loader,其类型是一个数组,数组里每一项都描述了如何去处理部分文件。每一项 rule 大致可以由以下三部分组成:- 条件匹配:通过
test
、include
、exclude
等配置来命中可以应用规则的模块文件; - 应用规则:对匹配条件通过后的模块,使用
use
配置项来应用 loader,可以应用一个 loader 或者按照从后往前的顺序应用一组 loader,还可以分别给对应 loader 传入不同参数; - 重置顺序:一组 loader 的执行顺序默认是从后到前(或者从右到左)执行,通过
enforce
选项可以让其中一个 loader 的执行顺序放到最前(pre)或者是最后(post)。
- 条件匹配:通过
loaders
loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。
module.exports = {
module: {
rules: [
// test 属性:用于标识出应该被对应的 loader 进行转换的某个或某些文件。
// use 属性:表示进行转换时,应该使用哪个 loader。
{ test: /\.css$/, use: 'css-loader' },
],
},
};
常用 loaders :
- 编译相关:
babel-loader
、ts-loader
- 样式相关:
style-loader
、css-loader
、less-loader
、postcss-loader
- 文件相关:
file-loader
、url-loader
plugins(插件)
plugins(插件) 目的在于解决 loader 无法实现的其他事。参与打包整个过程、打包优化和压缩、配置编译时的变量、极其灵活。
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 用于访问内置插件
const config = {
module: {
rules: [{ test: /\.txt$/, use: 'raw-loader' }],
},
plugins: [
new webpack.optimize.UglifyJsPlugin(),
new HtmlWebpackPlugin({ template: './src/index.html' }),
],
};
module.exports = config;
常用 plugins :
- 优化相关:
CommonsChunkPlugin
、UglifyjsWebpackPlugin
- 功能相关:
ExtractTextWebpackPlugin
、HtmlWebpackPlugin
、HotModuleReplacementPlugin
、CopyWebpackPlugin
模式
通过选择 development
或 production
之中的一个,来设置 mode
参数,可以启用相应模式下的 webpack 内置的优化。
target(构建的目标)
通过设置 target 来指定构建的目标(target),值有两种类型:string
和 function
。
string
类型值web
:默认,编译为类浏览器环境里可用node
:编译为类 Node.js 环境可用(使用 Node.js require 加载 chunk)async-node
:编译为类 Node.js 环境可用(使用 fs 和 vm 异步加载分块)electron-main
:编译为 Electron 主进程electron-renderer
:编译为 Electron 渲染进程node-webkit
:编译为 Webkit 可用,并且使用 jsonp 去加载分块。支持 Node.js 内置模块和 nw.gui 导入(实验性质)webworker
:编译成一个 WebWorker
function
类型:接收一个compiler
作为参数const webpack = require('webpack'); const options = { target: (compiler) => { compiler.apply( new webpack.JsonpTemplatePlugin(options.output), new webpack.LoaderTargetPlugin('web') ); }, };
externals
externals 提供了 从输出的 bundle 中排除依赖 的方法,减小打包文件的体积。
通常在开发自定义 js 库(library)的时候用到,用于去除自定义 js 库依赖的其他第三方 js 模块。
devtool
devtool 控制是否生成,以及如何生成 source map。使用 SourceMapDevToolPlugin 进行更细粒度的配置。常用配置值:
source-map
:原始源代码cheap-module-eval-source-map
:原始源代码(仅限行)
生产环境不使用或者使用 source-map(如果有 Sentry
这类错误跟踪系统),开发环境使用 cheap-module-eval-source-map