webpack 简介
概念
- webpack是一个用于
现代JavaScript应用程序的静态模块打包工具 - 当webpack处理应用程序时,它会在内部从一个或多个
入口点构建一个依赖图,然后将你项目中所需的每一个模块组合成一个或多个bundles,它们均为静态资源,用于展示你的内容。
模块:前端项目中任何文件。NpmPackange/SCSS/CSS/VUE/IMG/TS/JS
核心概念:
为什么选择webpack?
- 支持多种模块标准:包括AMD、CommonJS,以及最新的ES6模块
- 依赖自动收集:自动构建并基于你所引用或导出的内容推断出依赖的图谱
- 处理各种类型的资源:例如:images,fonts和stylesheets
- 关心性能和加载时间:它始终在改进或添加新功能,例如:异步地加载chuk和预取,以便为你的项 目和用户提供最佳体验
- 具有完备的代码分割解决方案
- 庞大的杜区支持:loader与plugin丰富
webpack 配置
entry
- entry.是配置模块的入口,可抽象成输入,Vebpack执行构建的第一步将从入 口开始搜寻及递归解析出所有入口依赖的模块
- entry配置是必填的,若不填则将导致Webpack报错退出。
//1个chunk
module.exports = {
entry: {
main: './src/index.js'
}
}
//1个chunk
module.exports = {
entry: ['./src/index1.js','./src/index2.js']
}
// 可能输出多个chunk
module.exports = {
entry: {
app: './src/index.js',
adminApp:['./src/admin1.js','./src/admin2']
}
}
output
- 可以通过配置output选项,告知webpack如何向硬盘写入编译文件。注意,即使可以存 在多个entry起点,但只能指定一个output配置。
- 包括以下两点:filename用于输出文件的文件名、目标输出目录path的绝对路径。
module.exports = {
output: {
filename: 'bundle.js'
}
}
module.exports = {
output: {
filename: '[name].js',
path: dirname + '/dist',
},
}
output.filename
- 只有一个输出文件,可以写成静态不变的如filename:'bundle.js
- 模版和变量可以针对多个Chunk要输出时情况,可以根据Chunk的名称来区分输出的文件 名filename:'[name]js
| 变量名 | 含义 |
|---|---|
| id | Chunk的唯一标识,从0开始 |
| name | Chunk的名称 |
| hash | Chunk的唯一标识Hash值 |
| chunkhash | Chunk内容的Hash值 |
output.path
output.path配置输出文件存放在本地的目录,必须是string类型的绝对路径。 通常通过Node.js的path模块去获取绝对路径:
//写入到硬盘 ./dist/app.js
module.exports = {
entry: {
app: './src/app.js'
},
output: {
filename: '[name].js',
path: __dirname + '/dist',
},
}
//是对资源使用CDN和hash的复杂示例
module.exports = {
//...
output: {
path: '/home/proj/cdn/assets/[fullhash]',
publicPath: "https://cdn.example.com/assets/[fullhash]/"
}
}
loader
loader用于对模块的源代码进行转换:
- loader可以使你在import或"load(加载)"模块时预处理文件。
- loader类似于其他构建工具中"任务(task)”,并提供了处理前端构建步骤的得力方式。
- loader可以将文件从不同的语言(如TypeScript)转换为JavaScript或将内联图像转换为 data URL
- loader甚至允浒你直接在JavaScript模块中import CSS文件!
npm install -S css-loader ts-loader
//通过module.rules来配置
module.exports = {
//...
module:{
rules:[
{test:/\.css$/,use:'css-loader'},
{test:/\.ts$/,use:'ts-loader'}
]
}
}
loader的使用
配置方式: 在你的应用程序中,有两种使用loader的方式:
- 配置方式(推荐):在webpack.config.js文件中指 定loader.
- 内联方式:在每个import语句中显式指定loader。
注意在webpack v4版本可以通过CLI使用loader, 但是在webpack v5中被弃用。
执行顺序: loader从右到左(或从下到上)地取值(evaluate)/执 行(execute)。在右边的示例中,从sass-loader开始执 行,然后继续执行css-loader,最后以style-loader 为结束。
module.exports = {
//...
module: {
rules: [
{
test: /\.css$/, use: [
{
//[style-loader](/loaders/style-loader)
loader: 'style-loader'
},
{
//[css-loader](/loaders/css-loader)
loader: 'css-loader',
options: {
module: true
}
},
{
// [sass-loader](/loaders/sass-loader)
loader: 'sass-loader'
},
]
},
]
}
}
loader 特性
- loader支持链式调用。链中的每个loader会将转换应用在已处理过的资源上。一组链式的loader将按照 相反的顺序执行。链中的第一个loader将其结果(也就是应用过转换后的资源)传递给下一个loader,依 此类推。最后,链中的最后一个loader,返▣webpack所期望的JavaScript
- loader可以是同步的,也可以是异步的。
- loader运行在Node.js中,并且能够执行任何操作。
- loader可以通过options对象配置(仍然支持使用query参数来设置选项,但是这种方式已被废弃)。
- 除了常见的通过package.json的main来将一个npm模块导出为loader,还可以在module.ules中使 用loader字段直接引用一个模块。
- 插件plugin)可以为loader带来更多特性。
- loader能够产生额外的任意文件。
loader 开发
- loader本质上是导出为函数的JavaScript模块。loader runner会调用此函数,然后将上一个
- loader产生的结果或者资源文件传入进去。函数中的this作为上下文会被webpack填充,并且
- loader runner中包含一些实用的方法,比如可以使loader调用方式变为异步,或者获取query 参数。
/**
* @param {string|Buffer} content源文件的内容
* @param {object} [map] 可以被https://github.com/mozilla/source-map使用的SourceMap数据
* @param {any} [meta] meta数据,可以是任何内容。
*/
function webpackLoader(content, map, meta) {
// your wepack loader code
}
同步loader
//如果是单个处理结果,可以在同步模式中直接返回。如果有多个处理结果,看下面this.callback()的示例。
module.exports = function (content, map, meta) {
return someSyncOperation(content);
}
// this.callback方法更灵活,它允许传递多个参数,而不只是content
module.exports = function (content, map, meta) {
this.callback(null, someSyncOperation(content), map, meta);
return;
}
异步loader
// 对于异步loader,使用this.async()来获取callback函数
module.exports = function (content, map, meta) {
let callback = this.asycn()
someAsyncOperation(content, function (err, res) {
if (err) return callback(err)
callback(null, res, map, meta)
})
}
//多个返回结果
module.exports = function (content, map, meta) {
let callback = this.asycn()
someAsyncOperation(content, function (err, res, sourceMaps, meta) {
if (err) return callback(err)
callback(null, res, sourceMaps, meta)
})
}
resolve
resolve.alias
创建import或require的别名,来确保引入模块变得简单。
resolve:{
alias:{
@components: './src/components/'
}
}
import Button from './src/components/button'
// ↓
import Button from '@components/button'
resolve.extensions
在导入语句没带文件后缀时,Webpack会自动带上后缀后去尝试访问文件是否存在。
resolve.extensions用于配置在尝试过程中用到的后缀列表
extensions:['.js','.json']
遇到import dafa from'/data'这样的导入语句时,Webpack会先去寻找/data.js文件,如果 该文件不存在就去寻找/data.json文件,如果还是找不到就报错
module.export = {
//...
resolve: {
extensions: ['.js', '.json', '.wasm']
}
}
resolve.modules
配置Webpack去哪些目录下寻找第三方模块,默认是只会去node_modules目录下寻找。 有时你的项目里会有一些模块会大量被其它模块依赖和导入,由于其它模块的位置分布不定, 针对不同的文件都要去计算被导入模块文件的相对路径,这个路径有时候会很长,就像这样import'../../../components/button'这时你可以利用modules配置项优化,假如那些被大 量导入的模块都在./src/components目录下,把modules配置成
const path = require('path')
module.export = {
//...
resolve: {
modules: [path.resolve(__dirname, 'src'), 'node_modules']
}
}
import '../../../components/button'
// ↓
import 'button'
resolve.enforceExtension
如果是true,将不允许无扩展名文件。默认如果./foo有.js扩展,require(./foo)可以正 常运行。但如果启用此选项,只有require('./foo.js)能够正常工作。
plugin
插件是webpack的支柱功能。插件目的在于 解决loader无法实现的其他事。Nebpack提 供很多开箱即用的插件。
用法:
由于插件可以携带参数/选项,你必须在
webpack配置中,向plugins属性传入一个
new实例.
ProgressPlugin用于自定义编译过程中的进度 报告,HtmlWebpackPlugin将生成一个 HTML文件,并在其中使用script引入一个名 为my-first-webpack.bundle.js的JS文件。
const HtmlWebpackPlugin = require('html-webpack-plugin');
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' }),
]
}
plugin 创建
- 一个JavaScript命名函数或JavaScript类。
- 在插件函数的prototype上定义一个apply方法。
- 指定一个绑定到webpack自身的事件钩子。
- 处理webpack内部实例的特定数据。
- 功能完成后调用webpack提供的回调。
//一个 JavaScript 类
class MyExamplewebpackPlugin {
//在插件函数的prototype上定义一个`apply`方法,以compiler为参数。
apply(compiler) {
//指定一个挂载到webpack自身的事件钩子。
compiler.hooks.emit.tapAsync(
'MyExamplewebpackPlugin',
(compilation, callback) => {
console.log("这里表示了资源的单次构建的`compilation`对象:", compilation);
//用webpack提供的插件API处理构建过程
compilation.addModule(/*...*/);
callback();
})
}
}
devServer
devServer.hot
启动webpack的热模块替换特性:
module.exports = {
//...
devServer: {
hot: true
}
}
// 通过命令行使用:
npx webpack serve --hot
// 如果需要禁用
npx webpack serve --no-hot
启用热模块替换功能,在构建失败时不刷新页面
作为回退,使用hot:'only'或者命令行 --host only
devServer.compress
启用gzip compression
module.exports = {
//...
devServer: {
compress: true
}
}
// 命令行启用
npx webpack serve -compress
// 禁用
npx webpack serve -compress
devServer.historyApiFallback
- 单页面应用,服务器在针对任何命中的路由时都返回一个对应的HTML文件
module.exports = {
//...
devServer: {
historyApiFallback: true
}
}
- 多个单页面组成,不同请求匹配不同的页面
module.exports = {
//...
devServer: {
historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html' },
{ from: /^\/subpage/, to: '/views/subpage.html' },
{ from: /./, to: '/views/404.html' },
]
}
}
}
devServer.headers
为所有的响应添加headers:
module.exports = {
//...
devServer: {
headers: {
'x-Custom-Foo': 'bar'
}
}
}
//or 一个数组
module.exports = {
//...
devServer: {
headers: [
{
key: 'X-Custom',
value: 'foo'
},
{
key: 'X-XXXXX',
value: 'xxx'
}
]
}
}
devServer.allowedHosts
允许访问开发服务器的服务列入白名单
当设置为allowedHosts:'auto'时,此配置项总是允许localhost、
host和client.webSocketURLhostname:
module.exports = {
//...
devServer: {
allowedHosts: ['xxx.com', 'xxx.host.com']
}
}
devServer.https
默认情况下,开发服务器将通过HTTP提供服务。可以选择使用HTTPS提供服务
module.exports = {
//...
devServer: {
https: true
}
}
但是你也可以提供自己的证书:
module.exports = {
//...
devServer: {
https: {
ca: './path/to/server.pem',
pfx: './path/to/server.pfx',
key: './path/to/server.key',
cert: './path/to/server.crt',
passphrase: 'webpack-dev-server',
requestCert: true,
}
}
}
devServer.open
告诉dev-server在服务器已经启动后打开浏览器。设置其为true来打开你的默认浏览器。
module.exports = {
//...
devServer: {
open: true
}
}
//在浏览器中打开指定页面
module.exports = {
//...
devServer: {
open: ['/my-page']
}
}
devServer.proxy
当拥有单独的API后端开发服务器并且希望在同一域上发送API请求时,代理某些URL可能会很有用
//对/api/users的请求会将请求代理到
http://localhost:3000/api/users
module.exports = {
//...
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
// 如果不希望传递'/api',则需要自己重写路径
pathRewrite: { '^/api': '' },
//默认情况下,将不接受在HTTPS上运行且证书无效的后端服务器。如果需要,可以这样修改配置
secure: false,
//有时不想代理所有内容。可以基于函数的返回值绕过代理。在该功能中,可以访问请球,响应和代理选项。
//1. 返回null或undefined以继续使用代理处理情球。
//2. 返回false会为请球产生404错误。
//3. 返回提供服务的路径,而不是继续代理请求。
bypass: function (req, res, proxyOptions) {
if (req.headers.accpet.indexOf('html') !== -1) {
console.log('Skipping proxy for browser request');
return './index.html'
}
}
}
}
}
}
mode
提供mode配置选项,告知webpack使用相应模式的内置优化。
| 选项 | 描述 |
|---|---|
| development | 会将DefinePlugin中process.env.NODE_ENV的值设置为development.为模块和chunk.启用有效的名。 |
| production | 会将DefinePlugin中process.env.NODE_ENV的值设置为production。为模块和chunk.启用确定性的混淆名称,FlagDependencyUsagePlugin, FlagIncludedChunksPlugin,ModuleConcatenationplugin, NoEmitonErrorsPluginTerserPlugin。 |
| none | 不使用任何默认优化选项 |
webpck原理
基本概念
- Entry:指示webpack应该使用哪个模块,来作为构建其内部依赖图(dependency graph) 的开始。进入入口起点后,webpack会找出有哪些模块和库是入口起点(直接和间接)依赖 的。
- Module:模块,在Webpack里一切皆模块,一个模块对应着一个文件。Webpack会从配 置的Ent女y开始递劃归找出所有依赖的模块。
- Chunk:代码块,一个Chunk由多个模块组合而成,用于代码合并与分割。
- Loader:模块转换器,用于把模块原内容按照需求转换成渐内容。
- Plugin:扩展插件,在Webpack构建流程中的特定时机会广播出对应的事件,插件可以监 听这些事件的发生,在特定时机做对应的事情。
流程概况
- 初始化参数:从配置文件和Shel语句中读取与合并参数,得出最终的参数:
- 开始编译:用上一步得到的参数初始化Compiler对像,加载所有配置的插件,执行对像的 un方法开始执行编泽;
- 确定入口:根据配置中的entry找出所有的入口文件:
- 编译块:从入口文件出发,调用所有配置的Loader对模块进行翻译,再找出该模块依赖 的模块,再递归本步骤直到所有入口依赖的文件都经过了本步的处理:
- 完城模块编译:在经过第4步使用Loader翻译完所有模块后,得到了每个模块被翻译后的 最终内容以及它们之间的依赖关系:
- 输出资源:根据入口和模块之间的依赖关系,组装一个个包含多个模块的Chuk,再把 每个Chuk转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
- 输出院城:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文 件系统
webpack 优化
缩小文件搜索范围
- Loader对文件的转换操作很耗时,需要让尽可能少的文件被Loader处理。
- 通过test、include、exclude三个配置项来命中Loader要应用规则的文件。 为了尽可能少的让文件被Loader处理
- 优化resolve可以减少默认尝试
module.exports = {
module: {
rules: [
{
// 如果项目源码中只有js文件就不要写成/\.jsx?$/,提升正则表达式性能
test: /\.js$ /,
//babel-loader支持缓存转换出的结果,通过cacheDirectory选项开启
use: ["babel-loader?cacheDirectory"],
//只对项目根目录下的5rc目录中的文件采用babel-loader
include: path.resolve(__dirname, "src"),
}
],
resolve: {
//尽可能的减少后缀尝试的可能性
extensions: ["js"],
}
}
}
区分环境
- 在开发过程中,切忌在开发环境使用生产环境才会用到的工具,如在开发环境下,应该排除 [fullhash]/[chunkhash]/[contenthash]等工具。
- 在生产环境,也应该避免使用开发环境才会用到的工具,如webpack-dev-server等插件。
外部拓展(Externals)
externals配置选项提供了「从输出的bundle中排除依赖」的方法。相反,所创建的bundle 依赖于那些存在于用户环境(consumer's environment)中的依赖。此功能通常对library开发 人员来说是最有用的,然而也会有各种各样的应用程序用到它。
例如,从CDN引入jQuery,而不是把它打包
module.exports = {
extrenals: {
jquery: 'jQuery'
}
}
分割代码按需加载
在给单页应用做按需加载优化时,一般采用以下原则:
- 把整个网站划分成一个个小功能,再按照每个功能的相关程度把它们分成几类。
- 把每一类合并为一个Chunk,按需加载对应的Chunk。
- 对于用户首次打开你的网站时需要看到的画面所对应的功能,不要对它们做按需 加载,而是放到执行入口所在的Chunk中,以降低用户能感知的网页加载时间。
- 对于个别依赖大量代码的功能点,例如依赖Chart,.js去画图表、依赖flv,js去播 放视频的功能点,可再对其进行按需加载
例如路由选择()=>import('')加载方式,当页面跳转相应页面再加载
缓存cache
通过配置webpack持久化缓存cache:filesystem,来缓存生成的webpack模块和chunk,改 善构建速度。
通过cache:filesystem可以将构建过程的webpack模板进行缓存,大幅提升二次构建速度、打 包速度,当构建突然中断,二次进行构建时,可以直接从缓存中拉取,可提速90%左右。
module.exports = {
caches: {
type: 'filesystem'
}
}
多进程
通过thread-loader将耗时的loader放在 一个独立的worker池中运行,加快loader 构建速度。
我们应该仅在非常耗时的loader前引入 thread-loader。
module.exports = {
rules: {
test: '...',
use: [
{
//...
},
{
loader: 'thread-loader',
options: {
workerParallelJobs: 2,
},
}
]
}
}
Webpack5 新增关键特性
内置静态资源构建能力一Asset Modules
在webpack5之前,通常使用:
- raw-loader将文件导入为字符串
- url-loader将文件作为data URI内联到bundle中
- file-loader将文件发送到输出目录
资源模块类型(asset module type),通过添加4种新的模块类型,来替换所有这些loader:
- asset/resource发送一个单独的文件并导出URL。之前通过使用file-loader实现。
- asset/inline导出一个资源的data URI。之前通过使用url-loader实现。
- asset/source导出资源的源代码。之前通过使用raw-loader实现。
- asset在导出一个data URI和发送一个单独的文件之间自动选择。之前通过使用url-loader,并且配置资 源体积限制实现。
文件缓存
在webpack4中,我们会使用cache-loader缓存一些性能开销较大的loader,或者是使用hard- source.-webpack-plugin为模块提供一些中间缓存。在Webpack5之后,默认就为我们集成了一 种自带的缓存能力(对module和chunks进行缓存)。通过如下配置,即可在二次构建时提速
module.exports = {
caches: {
//默认缓存到node modules,/.cache,/webpack中
//也可以自定义缓存目录,cache.cacheDirectory选项仅当cache.type被设置成filesystem才可用。
//cacheDirectory:path.resolve(dirname,'node_modules/.cac/webpack'),
type: 'filesystem',
buildDependencies: {
//2.将您的配置添加为buildDependency以使配置更政时缓存失效
config: [__filename]
//3.如架您有其他构建所依赖的东西你可以在这里添加它们
//清注意,webpack、加载器和从你的配置中引用的所有摸块都会自动添加
}
}
}
更好的tree-shaking
- webpack4没有分析模块的导出和导入之间的依赖关系。webpack5有一个新选项 optimization.innerGraph,它在生产模式下默认启用,它对模块中的符号运行分析以找出从导出 到导入的依赖关系。
- webpack5添加了对某些CommonJs结构的支持,允许消除未使用的CommonJs导出并跟踪 require0调用中引用的导出名称。
常用loader
- babel-loader: 把ES6转换成ES5
- css-loader: 加载CSS,支持模块化、压缩、文件导入等特性
- style-loader: 把CSS代码注入到JavaScript中,通过DOM操作去加载CSS
- sass-loader: 把SCSS/SASS代码转换成CSS
- postcss-loader: 扩展CSS语法,使用下代CSS
- less-loader: 把Less代码转换成CSS代码
- eslint-loader: 通过ESLint检查JavaScript代码
- vue-loader: 加载Vue.js单文件组件
- define-plugin: 定义环境变量
- ignore-plugin: 用于忽略部分文件
- commons-.chunk-plugin: 提取公共代码
- Web-webpack-plugin: 方便的为单页应用输出HTML
- hot-module-replacement-plugin: 开启模块热替换功能
- uglifyjs-webpack-plugin: 通过UglifyES压缩ES6代码