概念
webpack 是一个现代 JavaScript 应用程序的静态模块打包工具。当 webpack 处理应用程序时,它会在内部构建一个 依赖图(dependency graph),此依赖图会映射项目所需的每个模块,并生成一个或多个 bundle。
webpack主要使用场景是单页面富应用(SPA)。
简单来说,webpack就是一个.js配置文件,你的架构好或差都提现在这个配置里。
核心概念(最重要是前4个,一定要懂)
- 入口(entry)
- 输出(output)
- loader
- 插件(plugin)
- 模式(mode)
- 浏览器兼容性(browser compatibility)
安装
两种方式
- 运行
npm i webpack -g
全局安装webpack,这样就能在全局使用webpack的命令 - 在项目根目录中运行
npm i webpack --save-dev
安装到项目依赖中
配置
- 基本配置
var path = require('path');
module.exports = {
mode: 'development',
entry: './foo.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'foo.bundle.js'
}
};
- 多个target
可以将单个配置导出为 object, function 或 Promise,还可以将其导出为多个配置。
webpack实时打包构建
- 由于每次重新修改代码之后,都需要手动运行webpack打包的命令,比较麻烦,所以使用**
webpack-dev-server
来实现代码实时打包编译,当修改代码之后,会自动进行打包构建。** - 运行
npm i webpack-dev-server --save-dev
安装到开发依赖 - 安装完成之后,需要在
package.json
文件中配置运行webpack-dev-server
命令,在scripts节点下新增"dev": "webpack-dev-server"
指令。- 可以使用**
--contentBase src
**选项来修改dev指令,指定启动的根目录:(后面有更好的方法使用插件来指定启动页面) - **
--open
**选项表示自动打开浏览器 - **
--port 4321
**表示打开的端口号为4321 - **
--hot
**表示启用浏览器热更新
- 可以使用**
"dev": "webpack-dev-server --contentBase src --hot --port 4321 --open"
注意:发现可以进行实时打包,但是dist目录下并没有生成
bundle.js
文件,这是因为webpack-dev-server
将打包好的文件放在了内存中。
入口(entry)
入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始。
使用entry
属性设置入口。
单个入口
用法:entry: string|Array<string>
module.exports = {
entry: './path/to/my/entry/file.js'
};
等价于
module.exports = {
entry: {
main: './path/to/my/entry/file.js'
}
};
对象语法
用法:entry: {[entryChunkName: string]: string|Array<string>}
module.exports = {
entry: {
app: './src/app.js',
adminApp: './src/adminApp.js'
}
};
优点:这些配置可以重复使用,并且可以与其他配置组合使用。用于将关注点从环境(environment)、构建目标(build target)、运行时(runtime)中分离。然后使用专门的工具(如webpack-merge)将它们合并起来。
多页面应用程序
在 webpack < 4 的版本中,通常将 vendor(第三方库) 作为单独的入口起点添加到 entry 选项中,以将其编译为单独的文件(与
CommonsChunkPlugin
结合使用)。而在 webpack 4 中不鼓励这样做。而是使用optimization.splitChunks
选项,将 vendor 和 app(应用程序) 模块分开,并为其创建一个单独的文件。不要 为 vendor 或其他不是执行起点创建 entry。
module.exports = {
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js'
}
};
输出(output)
output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js
,其他生成文件默认放置在 ./dist
文件夹中。只指定一个 output
配置。
filename
用于输出文件名
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'
}
};
多个入口起点
如果配置创建了多个单独的 “chunk”(例如,使用多个入口起点或使用像 CommonsChunkPlugin 这样的插件),则应该使用 占位符(substitutions) 来确保每个文件具有唯一的名称。
module.exports = {
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name].js',
path: __dirname + '/dist'
}
};
选项
output: {
publicPath: string, // 输出解析文件的目录,url 相对于 HTML 页面
library: string, // 导出库(exported library)的名称
}
loader
webpack 只能理解 JavaScript 和 JSON 文件。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中。
配置
module.rules
允许你在 webpack 配置中指定多个 loader。
loader 从右到左地取值(evaluate)/执行(execute)。
test
属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。(正则表达式匹配)use
属性,表示进行转换时,应该使用哪个 loader。
const path = require('path');
// 在下面的示例中,从 sass-loader 开始执行,然后继续执行 css-loader,最后以 style-loader 为结束。
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
modules: true
}
},
{ loader: 'sass-loader' }
]
}
]
}
};
loader特性
- loader 支持链式传递。链中的每个 loader 会将转换应用在已处理过的资源上。一组链式的 loader 将按照相反的顺序执行。链中的第一个 loader 将其结果(也就是应用过转换后的资源)传递给下一个 loader,依此类推。最后,链中的最后一个 loader,返回 webpack 期望 JavaScript。
- loader 可以是同步的,也可以是异步的。
- loader 运行在 Node.js 中,并且能够执行任何 Node.js 能做到的操作。
- loader 可以通过
options
对象配置(仍然支持使用query
参数来设置选项,但是这种方式已被废弃)。 - 除了常见的通过
package.json
的main
来将一个 npm 模块导出为 loader,还可以在 module.rules 中使用loader
字段直接引用一个模块。 - 插件(plugin)可以为 loader 带来更多特性。
- loader 能够产生额外的任意文件。
常用loader
url-loader
像 file loader 一样工作,但如果文件小于限制,可以返回 data URL。file-loader
将文件发送到输出文件夹,并返回(相对)URL。babel-loader
加载 ES2015+ 代码,然后使用 Babel 转译为 ES5。html-loader
导出 HTML 为字符串,需要引用静态资源。style-loader
将模块的导出作为样式添加到 DOM 中。css-loader
解析 CSS 文件后,使用 import 加载,并且返回 CSS 代码。less-loader
加载和转译 LESS 文件。sass-loader
加载和转译 SASS/SCSS 文件。vue-loader
加载和转译 Vue 组件。jshint-loader
PreLoader,使用 JSHint 清理代码。
示例:
通过 npm 安装指定loader。
module: { // 用来配置第三方loader模块的
rules: [ // 文件的匹配规则
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},// 处理css文件的规则
{
test: /\.(png|jpg|gif)$/,
use: 'url-loader?limit=43960'
}, // 通过limit指定进行base64编码的图片大小;只有小于指定字节(byte)的图片才会进行base64编码
]
}
使用babel处理高级JS语法
-
运行
npm i babel-core babel-loader babel-plugin-transform-runtime --save-dev
安装babel的相关loader包 -
运行
npm i babel-preset-es2015 babel-preset-stage-0 --save-dev
安装babel转换的语法 -
在
webpack.config.js
中添加相关loader模块,其中需要注意的是,一定要把node_modules
文件夹添加到排除项:{ test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ }
-
在项目根目录中添加
.babelrc
文件,并修改这个配置文件如下:{ "presets":["es2015", "stage-0"], "plugins":["transform-runtime"] }
注意:语法插件babel-preset-es2015可以更新为babel-preset-env,它包含了所有的ES相关的语法;
插件(plugin)
loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量。
配置
使用一个插件,首先require()
进去,然后到 plugins
属性添加在数组中。多数插件可以通过选项(option)自定义。也可以在一个配置文件中因为不同目的而多次使用同一个插件,通过使用 new
操作符来创建它的一个实例。
const HtmlWebpackPlugin = require('html-webpack-plugin'); //通过 npm 安装
const webpack = require('webpack'); //访问内置的插件
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
filename: 'my-first-webpack.bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: 'babel-loader'
}
]
},
plugins: [
new webpack.ProgressPlugin(),
new HtmlWebpackPlugin({template: './src/index.html'})
]
};
扩展
使用html-webpack-plugin
插件配置启动页面
- 运行
npm i html-webpack-plugin --save-dev
安装到开发依赖 - 修改
webpack.config.js
配置文件如下:
// 导入处理路径的模块
var path = require('path');
// 导入自动生成HTMl文件的插件
var htmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: path.resolve(__dirname, 'src/js/main.js'), // 项目入口文件
output: { // 配置输出选项
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js' // 配置输出的文件名
},
plugins:[ // 配置插件
new htmlWebpackPlugin({
template:path.resolve(__dirname, 'src/index.html'),//模板路径
filename:'index.html'//自动生成的HTML文件的名称
})
]
}
模式(mode)
告知 webpack 使用相应环境的内置优化。如果不声明默认production模式,即生产模式(压缩)。
用法:mode: 'none' | 'development' | 'production'
module.exports = {
mode: 'production'
};
选项 | 描述 |
---|---|
development | 开发模式。会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development 。启用 NamedChunksPlugin 和 NamedModulesPlugin 。 |
production | 生产模式。会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production 。启用 FlagDependencyUsagePlugin , FlagIncludedChunksPlugin , ModuleConcatenationPlugin , NoEmitOnErrorsPlugin , OccurrenceOrderPlugin , SideEffectsFlagPlugin 和 TerserPlugin 。 |
none | 退出任何默认优化选项 |
解析(resolve)
resolver 帮助 webpack 从每个如 require
/import
语句中,找到需要引入到 bundle 中的模块代码。
resolve: {
// 解析模块请求的选项
// (不适用于对 loader 解析)
modules: [
"node_modules",
path.resolve(__dirname, "app")
],
// 用于查找模块的目录
extensions: [".js", ".json", ".jsx", ".css"],
// 使用的扩展名
alias: {
// 模块别名列表
// "别名": 模块或者js的绝对路径
"module": path.resolve(__dirname, "app/third/module.js"),
// 模块别名相对于当前上下文导入
},
}
—————————————————————— 一条华丽的分割线 ————————————————————————
实战
const path = require('path');
module.exports = {
// entry 表示入口,Webpack执行构建的第一步将从Entry开始,可抽象成输入
// 类型可以是string、object、array
entry: './app/entry', // 只有1个入口,入口只有1个文件
entry: ['./app/entry1', './app/entry2'], // 只有1个入口,入口有两个文件
entry: { // 有两个入口
a: './app/entry-a',
b: ['./app/entry-b1', './app/entry-b2']
},
// 如何输出结果:在Webpack经过一系列处理后,如何输出最终想要的代码
output: {
// 输出文件存放的目录,必须是string类型的绝对路径
path: path.resolve(__dirname, 'dist'),
// 输入文件的名称
filename: 'bundle.js', // 完整的名称
filename: '[name].js', // 在配置多个entry时,通过名称模板为不同的entry生成不同的文件名称
filename: '[chunkhash].js', // 根据文件内容的Hash值生成文件的名称,用于浏览器长时间缓存文件
// 发布到线上的所有资源的URL前缀,为string类型
publicPath: '/assets/', // 放到指定目录下
publicPath: '', // 放到根目录下
publicPath: 'https://cdn.example.com/', // 放到CDN上
// 导出库的名称,为string类型
// 不填它时,默认的输出格式是匿名的立即执行函数
library: 'MyLibrary',
// 导出库的类型,为枚举类型,默认是var
// 可以是umd、umd2、commonjs2、commonjs、amd、this、var、assign、window、global、jsonp
libraryTarget: 'umd',
// 是否包含有用的文件路径信息到生成的代码里,为boolean类型
pathinfo: true,
// 附加Chunk的文件名称
chunkFilename: '[id].js',
chunkFilename: '[chunkhash].js',
// JSONP异步加载资源是的回调函数名称,需要和服务端搭配使用
jsonpFunction: 'myWebpackJsonp',
// 生成的Source Map文件的名称
sourceMapFilename: '[file].map',
// 浏览器开发者工具里显示的源码模块名称
devtoolModuleFilenameTemplate: 'webpack:///[resource-path]',
// 异步加载跨域的资源时使用的方式
crossOriginLoading: 'use-credentials',
crossOriginLoading: 'anonymous',
crossOriginLoading: false,
},
// 配置模块相关
module: {
rules: [ // 配置Loader
{
test: /\.jsx?$/, // 正则匹配命中要使用Loader的文件
include: [ // 只会命中这里面的文件
path.resolve(__dirname, 'app')
],
exclude: [ // 忽略这里面的文件
path.resolve(__dirname, 'app/demo-files')
],
use: [ // 使用哪些Loader,有先后持续,从后向前执行
'style-loader', // 直接使用Loader的名称
{
loader: 'css-loader',
options: {
// 向html-loader传一些参数
}
}
]
},
],
noParse: [ // 不用解析和处理的模块
/special-library\.js$/ // 用正则匹配
],
},
// 配置插件
plugins: [],
// 配置寻找模块的规则
resolve: { // 寻找模块的根目录,为array类型,默认以node_modules为根目录
modules: [
'node_modules',
path.resolve(__dirname, 'app')
],
extensions: ['.js', '.json', '.jsx', '.css'], // 模块的后缀命名
alias: { // 模块别名配置,用于映射模块
// 将'module'映射成'new-module',同样,'module/path/file'也会被映射成'new-module/path/file'
'module': 'new-module',
// 使用结尾符号$后,将'only-module'映射成'new-module',
// 但是不像上面的,'module/path/file'不会被映射成'new-module/path/file'
'only-modules$': 'new-module'
},
alias: [ // alias还支持使用数组来更详细地进行配置
{
name: 'module', // 老模块
alias: 'new-module', // 新模块
// 是否只映射模块,如果是true,则只有'module'会被映射;如果是false,则'module/inner/path'也会被映射
onlyModule: true,
}
],
symlinks: true, // 是否跟随文件的软链接去搜寻模块的内容
descriptionFile: ['package.json'], // 模块的描述文件
mainFields: ['main'], // 模块的描述文件里描述入口的文件的字段名
enforceExtension: false, // 是否强制导入语句写明文件后缀
},
// 输出文件的性能检测配置
performance: {
hints: 'warning', // 有性能问题时输出警告
hints: 'error', // 有性能问题时出错错误
hints: 'false', // 关闭性能检查
maxAssetSize: 200000, // 最大文件的大小(单位为bytes)
maxEntrypointSize: 400000, // 最大入口文件的大小(单位为bytes)
assetFilter: function (assetFilename) { // 过滤要检查的文件
return assetFilename.endsWith('.css') || assetFilename.endsWith('.js');
}
},
devtool: 'source-map', // 配置source-map类型
context: __dirname, // Webpack使用的根目录,string类型必须是绝对路径
// 配置输出代码的运行环境
target: 'web', // 浏览器,默认
target: 'webworker', // WebWorker
target: 'node', // Node.js,使用`require`语句加载Chunk代码
target: 'async-node', // Node.js,异步加载Chunk代码
target: 'node-webkit', // nw.js
target: 'electron-main', // electron,主线程
target: 'electron-renderer', // electron,渲染线程
externals: { // 使用来自JavaScript运行环境提供的全局变量
jquery: 'jQuery'
},
stats: { // 控制台输出日志控制
assets: true,
colors: true,
errors: true,
errorDetails: true,
hash: true,
},
devServer: { // DevServer相关的配置
proxy: { // 代理到后端服务接口
'/api': 'http://localhost:3000'
},
contentBase: path.join(__dirname, 'public'), // 配置DevServer HTTP服务器的文件根目录
compress: true, // 是否开启Gzip压缩
historyApiFallback: true, // 是否开发HTML5 History API网页
hot: true, // 是否开启模块热替换功能
https: false, // 是否开启HTTPS模式
},
profile: true, // 是否捕捉Webpack构建的性能信息,用于分析是什么原因导致构建性能不佳
cache: false, // 是否启用缓存来提升构建速度
watch: true, // 是否监听
watchOptions: { // 监听模式选项
// 不监听的文件或文件夹,支持正则匹配。默认为空
ignored: /node_modules/,
// 监听到变发生后,等300ms再执行动作,截流,防止文件更新太快导致重新编译频率太快,默认为300ms
aggregateTimeout: 300,
// 不停地询问系统指定的文件有没有发生变化,默认每秒询问1000次
poll: 1000,
}
}