原理
模块化 是 webpack 实现的前提和核心的理念,webpack处理任何资源都是将其作为模块来操作。
webpack打包核心流程可以概括为:分析配置,解析代码,查找依赖,生成模块,打包文件。
webpack打包流程执行原理示例:
/* 初始化配置:
运行打包shell命令行,传入打包参数
读取打包配置文件
对配置进行处理,合并生成最终配置
*/
// ...默认使用webpack.config.js的webpack配置
const webpackConfig = require('webpack.config.js');
/* 实例化编译器:
使用合成的配置初始化Compiler对象实例
加载配置中所需插件
*/
// ...默认使用本地webpack-cli,引入webpack模块
const webpack = require('webpack');
// 传入webpack配置,运行webpack模块,返回compiler实例
const compiler = webpack(webpackConfig);
/* 执行编译:
运行Compiler.run()方法,从entry配置项指定的入口文件开始执行编译,
使用指定loader对相应文件进行解析,分析模块依赖,
递归执行此操作,最终所有入口文件依赖的所有模块全部解析和编译完成,
所有模块编译后的内容和相互依赖关系确定。
*/
// 开始执行编译
compiler.run((err, stats)=>{
// stats为compiler.getStats()方法(内部调用lib/Stats.js类创建实例)返回的实例对象
/* stats重要属性:
entries
chunks
modules
assets:{}
*/
});
/* 输出模块:
根据依赖关系和模块体积,将模块进行组合、拆分为多个chunk,
将每个chunk加入文件生成列表,准备生成资源。
此步骤是输出内容最后一个可修改阶段。
*/
/* 生成文件:
将文件生成列表按照指定路径和名称写入文件系统。
*/
概念
- Entry:webpack构建起始文件,代码依赖关系起点。可以设置多入口,生成多套依赖关系。
- Output:webpack构建文件的存放目录和命名。
- Loaders:webpack通过loader将js以外各类资源读取为可处理模块对象。
- Plugins:对webpack功能进行增强和补充,帮助webpack实现打包性能优化,打包体积优化,打包分析等功能。
- Chunks:webpack对代码模块化进行打包优化的重要方式,如复用模块分离去重,第三方模块分离缓存,异步模块分离加载等。
配置
const path = require('path');
module.exports = {
// 环境标识: development / production / ...
// 如果执行的shell脚本命令包含环境标识则优先使用shell参数
mode: 'development',
/* 调试工具:是否 和 如何 生成 source map
source-map:生成map文件,标识异常代码对应源文件中的行和列
eval-source-map:不生成map文件,标识 行 和 列
cheap-module-source-map:生成map文件,标识 行
cheap-module-eval-source-map:不生成map文件,标识 行
开启调试工具可能会影响
*/
devtool: 'eval-cheap-source-map',
// 入口文件:打包起始文件,依赖图起点
entry: './src/index.js',
// 输出配置
output: {
// 打包文件输出目录
path: path.resolve(__dirname, './dist'),
// 打包输出文件名
filename: '[name]_[hash:8].js',
// 打包依赖文件名
chunkFilename: '[name]_[contenthash:8].js',
// 外部资源路径
publicPath: path.resolve(__dirname, './dist'), // 相对于服务器根目录
// publicPath: "./dist/", // 相对于模板文件 app.html
// publicPath: 'https://cdn.example.com/assets/', // CDN(总是 HTTPS 协议)
// publicPath: "//cdn.clound.com/dist/", // CDN(协议相同)
},
// 模块解析配置
module: {
/* 指定不需要解析的模块:对于第三方库或已经处理过的静态资源,
不需要被解析,提高打包效率
注意:此配置将排除对指定文件进行模块解析处理,
直接使用模块源代码进行打包,减少的webpack的工作量,
不会减少打包体积,因为指定文件不会被排除打包
*/
noParse: /jquery/,
// loader配置
rules: [
{
test: /\.(js|jsx)$/,
/* include :指定需要执行的目录
exclude :指定不需要执行的目录
对于node_modules或已经处理过的静态资源,不需要再次执行babel,
通过配置exclude排除以提升效率,
注意:此配置将排除对指定文件使用当前loader进行处理,
减少的webpack的工作量,不会减少打包体积,
因为指定文件不会被排除打包
*/
exclude: /node_modules/,
// loader:"babel-loader",
// options: { presets: ["@babel/env"] },
// 多个 loader 用 use 数组按执行顺序倒叙加载
use: [
'thread-loader', // 使用多线程loader
'cache-loader', // 将编译结果缓存到磁盘,再次构建时如果文件没有发生变化则会直接使用缓存
{
loader: 'babel-loader',
options: { presets: ['@babel/env'] },
},
],
},
{
test: /\.css$/,
use: [
'cache-loader', // 将编译结果缓存到磁盘,再次构建时如果文件没有发生变化则会直接使用缓存
// process.env.NODE_ENV === "development" ? "style-loader" :
MiniCssExtractPlugin.loader,
// "css-loader?modules&localIdentName=[name]_[local]_[hash:base64:4]&importLoaders=1", // 字符串形式
{
// 对象形式
loader: 'css-loader',
options: {
importLoaders: 2, // 在 css-loader 前应用的 loader 的数量
modules: true, // 开启css-modules
localIdentName: '[name]_[local]_[md5:contenthash:base64:4]', // 命名规则
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
// 使用最新 css属性, 该插件已经包含autoprefixer(根据 Can I Use 中各大厂商兼容性自动添加前缀)
'postcss-preset-env',
'cssnano', // 压缩
'stylelint', // 格式化
/* 支持字符串 数组 CommonJS包 函数,详见postcss配置
["postcss-short", { prefix: "x" }],
require.resolve("my-postcss-plugin"),
myOtherPostcssPlugin({ myOption: true }),
*/
],
},
},
},
],
},
{
test: /\.less$/,
use: [
'cache-loader',
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 2,
modules: true,
localIdentName: '[name]_[local]_[md5:contenthash:base64:4]',
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: ['postcss-preset-env', 'cssnano', 'stylelint'],
},
},
},
'less-loader',
],
},
],
},
// 插件配置
plugins: [
new CleanWebpackPlugin(), // 每次打包自动清理上次输出文件
/* new CopyWebpackPlugin( {
patterns: [
// 复制静态文件
{ from: path.resolve( __dirname, './static/fonts' ) },
]
}), */
/* 指定排除打包的文件,减小打包体积
打包时将 moment 引入.locale 语言包排除
在项目代码中使用moment时,需要手动引用中文包 import "moment/locale/zh-cn"
否则无法设置语言为中文 moment.locale('zh-cn')
这样相当于将于只打包了语言包中的中文部分
*/
new webpack.IgnorePlugin(/\.\/locale/, /moment/),
new MiniCssExtractPlugin({
// 将css分离成单独文件,不依赖于 JS
filename: '[name]_[contenthash:8].css',
chunkFilename: '[name]_[contenthash:8].css',
}),
new HtmlWebpackPlugin({
// 输出 html文件,每一个HtmlWebpackPlugin实例对应一个页面,实现多页面打包
template: path.resolve(__dirname, './src/index.html'), // 入口模板文件
// templateContent:'<div id="root"></div>',// 入口模板内容 不能和模板文件同时配置
filename: 'index.html',
}),
new webpack.BannerPlugin('版权声明,将被插入到所有打包生成的js文件开头'),
// new BundleAnalyzerPlugin() // 打包体积分析
],
resolve: {
/* 指定模块路径:默认['node_modules']
从当前目录 ./node_modules 查找,到上级目录 ../node_modules,最后到根目录(即 npm 查找包的规则)
直接指定项目根目录,可以减少查找耗时
*/
modules: [path.resolve(__dirname, 'node_modules')],
/* 指定扩展名:默认['js', 'json']
导入模块没有后缀时,Webpack 会使用extensions后缀列表尝试匹配文件,
如:require('./data') 时 ,Webpack 会先尝试寻找 data.js,没有再去找 data.json;
列表越长,或者正确的后缀越往后,尝试的次数就会越多。
频率出现高的文件后缀优先放在前面。
列表尽可能的少,例如只有 3 个:js、jsx、json。
书写导入语句时,尽量写上后缀名。
*/
extensions: ['*', '.js', '.jsx'],
},
/**
* 监听变化:代码变更自动打包
* devServer将打包结果保存在内存中,不创建打包文件
* 开启监听自动打包可以查看实时产生的打包文件
*/
// watch: true,
/**
* 监听选项
*/
// watchOptions: {
// poll: 10, // 每秒检测变化次数
// aggregateTimeout: 1000, // 用于防抖的超时时间 ms
// ignored: /node_modules/, // 忽略监听的目录
// },
// optimization: {
// splitChunks: {
// cacheGroups: {
// common: {
// chunks: 'initial', // 'async'
// minSize: 1,
// minChunks: 1,
// },
// vendor: {
// priority: 1,
// test: /node_modules/,
// chunks: 'initial', // 'async'
// minSize: 1,
// minChunks: 1,
// },
// },
// },
// minimizer: [
// new TerserPlugin({
// // webpack默认代码压缩插件为uglifyjs
// // TerserPlugin可以实现多进程并行压缩
// parallel: 4, // 开启多进程并行压缩
// }),
// ],
// },
};
// module.exports = NewSpeedMeasurePlugin.wrap(config); // 打包速度分析打包
module.exports = config; //打包速度分析打包
安装node
初始化项目
npm init -y
安装webpack到项目
npm install webpack webpack-cli --save-dev
配置package.json
文件
设置private为true
,移除 main
入口
{
"name": "webpack-demo",
"version": "1.0.0",
"description": "",
"private": true, //配置private为true
//"main": "index.js",//并且移除 main 入口
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.0.1",
"webpack-cli": "^2.0.9"
},
"dependencies": {}
}
安装jquery到项目
npm install --save jquery
安装一个用于生产环境的安装包,应使用
npm install --save
安装一个用于开发环境的安装包(例如,linter, 测试库等),应使用
npm install --save-dev
详见 npm 文档
调整目录结构
src
用于存放项目源代码,dist用于存放
webpack构建后输出的优化后的代码(最终将在浏览器中加载使用的)
webpack-demo
|- package.json
|- /dist
|- index.html
|- /src
|- index.js
在index.js中引入jquery
import $ from 'jquery'
编辑index.html文件
<!doctype html>
<html>
<head>
<title>webpack</title>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
执行npx命令
npx webpack
此时访问index.html使用的已经是webpack输出的项目文件了
创建配置文件webpack.config.js
webpack-demo
|- package.json
|- webpack.config.js
|- /dist
|- index.html
|- /src
|- index.js
编辑配置文件webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
使用npx命令
npx webpack --config webpack.config.js
此时访问index.html使用的是webpack按照webpack.config.js中的配置输出的项目文件
注意,在 windows 中通过路径调用
webpack
时,必须使用反斜线。如node_modules\.bin\webpack --config webpack.config.js
。
如果
webpack.config.js
存在,则webpack
命令将默认选择使用它。我们在这里使用--config
选项只是向你表明,可以传递任何名称的配置文件。这对于需要拆分成多个文件的复杂配置是非常有用。
使用NPM 脚本
修改package.json文件
{
"name": "webpack-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack" //增加build命令,设置为执行webpack
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.0.1",
"webpack-cli": "^2.0.9",
"lodash": "^4.17.5"
}
}
调用新增的脚本命令
npm run build
通过向
npm run build
命令和你的参数之间添加两个中横线,可以将自定义参数传递给 webpack,例如:npm run build -- --colors
。
加载CSS
安装 style-loader 和 css-loader
npm install --save-dev style-loader css-loader
修改webpack.config.js文件
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: { //增加module配置
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
}
};
webpack 根据正则表达式,来确定应该查找哪些文件,并将其提供给指定的 loader。在这种情况下,以
.css
结尾的全部文件,都将被提供给style-loader
和css-loader
。
创建style.css文件
webpack-demo
|- package.json
|- webpack.config.js
|- /dist
|- bundle.js
|- index.html
|- /src
|- style.css //添加样式文件到源代码目录
|- index.js
|- /node_modules
编辑index.js文件
import $ from 'jquery';
import './style.css'; //引入样式文件
引入平级目录下文件要在路径开头写 ./ ,不能直接引用
使用npm脚本
npm run build
此时访问index.html已经具备了style.css文件中的样式
这时的样式是通过js添加在index.html中一个style标签来实现的,在生产环境中多数情况下需要使用ExtractTextWebpackPlugin把使用 CSS文件分离,以便节省加载时间。此外,现有的 loader 可以支持postcss, sass 和 less等任何CSS 处理器风格 。
加载图片 字体 数据
安装 file-loader (图片 和 字体)csv-loader ( CSV和TSV) xml-loader(XML),NodeJS内置支持JSON格式数据
npm install --save-dev file-loader
npm install --save-dev csv-loader xml-loader
编辑webpack.config.js文件
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}, //在module中增加file-loader处理规则
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
]
},
{
test: /\.(csv|tsv)$/,
use: [
'csv-loader'
]
},
{
test: /\.xml$/,
use: [
'xml-loader'
]
}
]
}
};
在项目目录增加相关资源文件
在index.js文件中引入
import $ from 'jquery';
import './style.css';
import Icon from './icon.png';
import DataXML from './data.xml';
import DataJSON from './data.json';//NodeJS内置支持JSON格式数据
当使用 import Icon from './icon.png'时
,该图像将被处理并添加到 output
目录,Icon
变量将包含该图像在处理后的最终 url。
当使用 css-loader 时,loader会以相同的方式处理 CSS 中的 url('./icon.png')
,将 './icon.png'
路径,替换为输出
目录中图像的最终url。
当使用html-loader时,loader会以相同的方式处理HTML中的<img src="./icon.png" />
,将 './icon.png'
路径,替换为输出
目录中图像的最终url。
此外,还以通过 image-webpack-loader 和 url-loader 来压缩和优化图像 。
在使用 d3 等工具来实现某些数据可视化时,预加载数据会非常有用。我们可以不用再发送 ajax 请求,然后于运行时解析数据,而是在构建过程中将其提前载入并打包到模块中,以便浏览器加载模块后,可以立即从模块中解析数据。
管理输出-重构