之前做的两个项目都是用create-react-app脚手架生成,无需关注配置,直接写业务就ok,现在有新项目了,要自己配置,那就只能看看官网,再看看解析教程之类的,先简单了解一下webpack的基础。
1.是什么
webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部构建一个 依赖图(dependency graph),此依赖图对应映射到项目所需的每个模块,并生成一个或多个 bundle。
从官方介绍可知,webpack就是一个静态模块的打包工具,就是图片,css,js等生成 JS 格式的 bundler 文件。这是其基础的核心作用。
构建的核心流程
初始化阶段:
- 初始化参数:从配置文件、 配置对象、Shell 参数中读取,与默认配置结合得出最终的参数
- 创建编译器对象:用上一步得到的参数创建
Compiler
对象 - 初始化编译环境:包括注入内置插件、注册各种模块工厂、初始化 RuleSet 集合、加载配置的插件等
- 开始编译:执行
compiler
对象的run
方法 - 确定入口:根据配置中的
entry
找出所有的入口文件,调用compilition.addEntry
将入口文件转换为dependence
对象
构建阶段:
- 编译模块(make):根据
entry
对应的dependence
创建module
对象,调用loader
将模块转译为标准 JS 内容,调用 JS 解释器将内容转换为 AST 对象,从中找出该模块依赖的模块,再 递归 本步骤直到所有入口依赖的文件都经过了本步骤的处理 - 完成模块编译:上一步递归处理所有能触达到的模块后,得到了每个模块被翻译后的内容以及它们之间的 依赖关系图
生成阶段:
- 输出资源(seal):根据入口和模块之间的依赖关系,组装成一个个包含多个模块的
Chunk
,再把每个Chunk
转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会 - 写入文件系统(emitAssets):在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
2.核心概念
1. entry (入口)
webpack根据此配置,找到构建其内部依赖图的开始,默认值是./src/index.js,可以配置一个或多个不同的入口起点。
单个入口的写法
module.exports = {
entry: './path/to/my/entry/file.js',
};
module.exports = {
entry: {
main: './path/to/my/entry/file.js',
},
};
多个入口的写法
module.exports = {
entry: ['./src/file_1.js', './src/file_2.js'],
output: {
filename: 'bundle.js',
},
};
module.exports = {
entry: {
app: './src/app.js',
adminApp: './src/adminApp.js',
},
};
可配置的对象属性
-
dependOn: 当前入口所依赖的入口。它们必须在该入口被加载前被加载。
-
filename: 指定要输出的文件名称。
-
import: 启动时需加载的模块。
-
library: 指定 library 选项,为当前 entry 构建一个 library。
-
runtime: 运行时 chunk 的名字。如果设置了,就会创建一个新的运行时 chunk。在 webpack 5.43.0 之后可将其设为 false 以避免一个新的运行时 chunk。
-
publicPath: 当该入口的输出文件在浏览器中被引用时,为它们指定一个公共 URL 地址。请查看 output.publicPath。
需要注意的是
-
runtime 和 dependOn 不应在同一个入口上同时使用
-
确保 runtime 不能指向已存在的入口名称
-
dependOn 不能是循环引用的
使用场景
1.分离 app(应用程序) 和 vendor(第三方库) 入口
module.exports = {
entry: {
main: './src/app.js',
vendor: './src/vendor.js',
},
};
这样你就可以在 vendor.js 中存入未做修改的必要 library 或文件(例如 Bootstrap, jQuery, 图片等),然后将它们打包在一起成为单独的 chunk。内容哈希保持不变,这使浏览器可以独立地缓存它们,从而减少了加载时间。
2.多页面应用程序
module.exports = {
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js',
},
};
在多页面应用程序中,server 会拉取一个新的 HTML 文档给你的客户端。页面重新加载此新文档,并且资源被重新下载。然而,这给了我们特殊的机会去做很多事,例如使用 optimization.splitChunks 为页面间共享的应用程序代码创建 bundle。由于入口起点数量的增多,多页应用能够复用多个入口起点之间的大量代码/模块,从而可以极大地从这些技术中受益。
2.output(输出)
有入口就有输出,output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。
即使可以存在多个 entry 起点,但只能指定一个 output 配置。
在 webpack 配置中,output 属性的最低要求是,将它的值设置为一个对象,然后为将输出文件的文件名配置为一个 output.filename
module.exports = {
output: {
filename: 'bundle.js',
},
};
多个入口起点
如果配置中创建出多于一个 "chunk"(例如,使用多个入口起点或使用像CommonsChunkPlugin 这样的插件),则应该使用 占位符(substitutions) 来确保每个文件具有唯一的名称
module.exports = {
entry: {
app: './src/app.js',
search: './src/search.js',
},
output: {
filename: '[name].js',
path: __dirname + '/dist',
},
};
// 写入到硬盘:./dist/app.js, ./dist/search.js
高级进阶
对资源使用 CDN 和 hash 的复杂示例
module.exports = {
//...
output: {
path: '/home/proj/cdn/assets/[fullhash]',
publicPath: 'https://cdn.example.com/assets/[fullhash]/',
},
};
如果在编译时,不知道最终输出文件的 publicPath 是什么地址,则可以将其留空,并且在运行时通过入口起点文件中的 __webpack_public_path__ 动态设置。
__webpack_public_path__ = myRuntimePublicPath;
// 应用程序入口的其余部分
3.loader
webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中
const path = require('path');
module.exports = {
output: {
filename: 'my-first-webpack.bundle.js',
},
module: {
rules: [{ test: /\.txt$/, use: 'raw-loader' }],
},
};
test 属性,识别出哪些文件会被转换,use 属性,定义出在进行转换时,应该使用哪个 loader。
4.plugin(插件)
插件可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量。
插件是 webpack 的 支柱 功能。webpack 自身也是构建于你在 webpack 配置中用到的相同的插件系统之上! 插件目的在于解决 loader 无法实现的其他事。
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 用于访问内置插件
module.exports = {
module: {
rules: [{ test: /\.txt$/, use: 'raw-loader' }],
},
plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })],
};
5.mode(模式)
通过选择 development, production 或 none 之中的一个,来设置 mode 参数,其默认值为 production
module.exports = {
mode: 'production',
};
3.configuration(配置)
基本配置
const path = require('path');
module.exports = {
mode: 'development',
entry: './foo.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'foo.bundle.js',
},
};
4.项目实践
首先将配置文件分成三部分,分别是webpack.dev.js(开发),webpack.prod.js(生产),webpack.config.js(公共)
webpack.dev.js
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const common = require('./webpack.config.js');
const path = require('path');
module.exports = webpackMerge.merge(common, { mode: 'development',
entry: {
app: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dev'),
filename: 'app.js',
publicPath: '/'
},
devServer: {
contentBase: './dev',
historyApiFallback: {
index: 'index.html'
},
hot: true,
inline: true,
port: 8888
},
plugins: [
new webpack.DefinePlugin({
"NODE_ENV": JSON.stringify('development')
}),
new webpack.HotModuleReplacementPlugin()
]
});
webpack.prod.js
const webpack = require('webpack');
const {merge} = require('webpack-merge');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const common = require('./webpack.config.js');
const path = require('path');
module.exports = merge(common, {
devtool: 'source-map',
mode: 'production',
entry: {
app: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist/'),
filename: 'bundle/[name].[chunkhash].js',
publicPath: '/'
},
optimization: {
splitChunks: {
chunks: "all",
}
},
plugins: [
new webpack.DefinePlugin({
"NODE_ENV": JSON.stringify('production')
}),
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: [
'bundle/*',
]
}),
new HtmlWebpackPlugin({
title: '',
inject: true,
template: 'dev/index.html',
filename: 'index.html'
}),
]
});
webpack.config.js
const path = require('path');
module.exports = {
module: {
rules: [
{
test: /\.(js)$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: [
'@babel/plugin-transform-runtime',
'@babel/plugin-proposal-class-properties',
'@babel/plugin-transform-named-capturing-groups-regex'
]
},
},
{
test: /\.(css)$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(scss)$/,
use: ['style-loader', 'css-loader', 'sass-loader']
},
{
test: /\.(less)$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.(png|jpg|gif)$/,
use: ['file-loader']
},
{
test: /\.(html)$/,
use: ['html-loader']
}
]
},
};
现在的版本不代表最终上线版本,后面会根据具体情况增减,遇到不会的,百度找找方案,学习一下,毕竟是个工具。
参考资料: