前言
上一篇文章讲的如何使用 webpack 项目地址: 搭建一个简易的项目使用 webpack4 从0开始搭建 react 项目。这一篇将基于上篇文章项目继续做项目优化、环境区分。
一、分离 css 样式,生成 css 文件
项目中我们一般使用 less/sass 来写样式,这里的话以 less 为例,首先我们安装
npm i less less-loader -D
使用 less-loader
{
test: /\.(css|less)$/,
use: ['style-loader', 'css-loader', 'less-loader']
}
在 入口文件 中引入 less 文件
// index.less
body {
background: red;
div {
display: flex;
color: #fff;
}
span {
color: blue;
}
}
postcss-loader 帮你将现代 CSS 语法转换成大多数浏览器都能理解的东西,根据你的目标浏览器或运行时环境来确定你需要的 polyfills,基于 cssdb 实现。 这里的话
使用 postcss-loader 来做浏览器适配,autoprefixer 会自动增加浏览器前缀。
现在样式还是通过 js 生成 style标签插入到页面当中,可以使用 MiniCssExtractPlugin 来生成 css 样式
npm i mini-css-extract-plugin postcss-loader autoprefixer -D
在 webpakc.config.js 中配置
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module: {
rules: [
{
test: /\.(css|less)$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
// 您可以在此处指定publicPath
// 默认情况下,它在webpackOptions.output中使用publicPath
publicPath: '../'
},
// 这里会直接到 src 文件下找 less/css 文件进行编译,这里是项目优化的一个小技巧
include: path.resolve(__dirname, './src')
}, 'css-loader', 'postcss-loader', 'less-loader']
},
]
}
plugins: [
new MiniCssExtractPlugin({
// 选项类似于webpackOptions.output中的相同选项
// 所有选项都是可选的
filename: "[name].css",
chunkFilename: "[id].css",
ignoreOrder :false ,// 启用以删除有关顺序冲突的警告
})
]
根目录下新建 postcss.config.js 来配置 postcss
module.exports = {
plugins: [
require('autoprefixer')({
overrideBrowserslist: ['last 2 versions', '>1%']
})
]
}
现在只需要将 css 压缩并且开启 tree shanking (摇树)功能,Css就配置完成了。
安装
// tree shanking 需要的插件
npm i glob-all purifycss-webpack purify-css -D
// cssnano 将你的 CSS 文件做 多方面的的优化,以确保最终生成的文件 对生产环境来说体积是最小的。
// web️对于webpack v3或更低版本,请使用optimize-css-assets-webpack-plugin@3.2.0。该optimize-css-assets-webpack-plugin@4.0.0版本及以上支持的WebPack V4。
npm i optimize-css-assets-webpack-plugin cssnano -D
在 webpack.config.js 中使用
// 压缩 Css 文件
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
// 用于处理多路径文件,使用purifycss的时候要用到glob.sync方法。
const glob = require('glob-all')
// Css tree shanking 摇树
const purifyCss = require('purifycss-webpack')
{
plugins: [
// 压缩css文件
new OptimizeCssAssetsWebpackPlugin({
cssProcessor: require('cssnano'),
cssProcessorPluginOptions: {
// 去掉注释
preset: ["default", { discardComments: { removeAll: true } }]
}
}),
new purifyCss({
paths: glob.sync([
path.resolve(__dirname, './*html'),
path.resolve(__dirname, './src/*js')
])
})
]
}
二、Optimization
通过webpack打包提取公共代码
optimization: {
// js 开启 tree shanking
usedExports: true,
splitChunks: {
chunks: "all", // 代码分隔 公共代码分离出来
name: true,
cacheGroups: {
// [\\/] 解决系统之间的兼容
react: {
test: /[\\/]react|react-dom[\\/]/,
name: 'react'
},
lodash: {
test: /[\\/]lodash[\\/]/,
name: 'lodash'
}
}
}
},
minimize
如果mode是production类型,minimize的默认值是true,执行默认压缩,
minimizer
允许你使用第三方的压缩插件,可以在optimization.minimizer的数组列表中进行配置
splitChunks
splitChunks: {
chunks: "all", // 默认作用于异步chunk,值为all/initial/async/function(chunk),值为function时第一个参数为遍历所有入口chunk时的chunk模块,chunk._modules为chunk所有依赖的模块,通过chunk的名字和所有依赖模块的resource可以自由配置,会抽取所有满足条件chunk的公有模块,以及模块的所有依赖模块,包括css
minSize: 30000, //表示在压缩前的最小模块大小,默认值是30kb
minChunks: 1, // 表示被引用次数,默认为1;
maxAsyncRequests: 5, //所有异步请求不得超过5个
maxInitialRequests: 3, //初始话并行请求不得超过3个
automaticNameDelimiter:'~',//名称分隔符,默认是~
name: true, //打包后的名称,默认是chunk的名字通过分隔符(默认是~)分隔
cacheGroups: { //设置缓存组用来抽取满足不同规则的chunk,下面以生成common为例
common: {
name: 'common', //抽取的chunk的名字
chunks(chunk) { //同外层的参数配置,覆盖外层的chunks,以chunk为维度进行抽取
},
test(module, chunks) { //可以为字符串,正则表达式,函数,以module为维度进行抽取,只要是满足条件的module都会被抽取到该common的chunk中,为函数时第一个参数是遍历到的每一个模块,第二个参数是每一个引用到该模块的chunks数组。自己尝试过程中发现不能提取出css,待进一步验证。
},
priority: 10, //优先级,一个chunk很可能满足多个缓存组,会被抽取到优先级高的缓存组中
minChunks: 2, //最少被几个chunk引用
reuseExistingChunk: true,// 如果该chunk中引用了已经被抽取的chunk,直接引用该chunk,不会重复打包代码
enforce: true // 如果cacheGroup中没有设置minSize,则据此判断是否使用上层的minSize,true:则使用0,false:使用上层minSize
}
}
}
更多配置请参考 webpack-optimizationsplitchunks
三、resolve
resolve: {
// 规定在那里寻找第三方模块
modules: [path.resolve(__dirname, './node_modules')],
// 别名 我们可以通过别名的方式快速定位到引用包的/方法的路劲,优化打包和运行本地服务
alias: {
react: path.resolve(__dirname, './node_modules/react/umd/react.production.min.js'),
'react-dom': path.resolve(__dirname, './node_modules/react-dom/umd/react-dom.production.min.js'),
'@': path.resolve(__dirname, './src')
},
// 自动补齐后缀名,这个列表会让webpack一级一级寻找,尽量少配置
extensions: ['.js', '.jsx']
},
更多配置请参考 webpack-resolve
四、js 开启 tree shanking
webpack 内置了 js 的摇树功能,在生产环境下,Tree-shaking会进行自动删除的操作
如果通过ES6的import引用的方式就会把没有用到的代码给删除掉。
在 package.json 中配置
{
"sideEffects": false
}
打包后会发现引用的 less 文件也会被过滤。因为webpack 人为 less 文件引用但是未被使用。
修改一下 sideEffects 的值
{
"sideEffects": [
"*.css",
"*.less"
]
}
五、DllPlugin
引用官方描述:
这个插件是在一个额外的独立的 webpack 设置中创建一个只有 dll 的 bundle(dll-only-bundle)。 这个插件会生成一个名为 manifest.json 的文件,这个文件是用来让 DLLReferencePlugin 映射到相关的依赖上去的。
简单说就是讲公共依赖缓存起来,不用每次运行都打包一遍
根目录下新建 webpack.dll.config.js
const path = require('path')
const { DllPlugin } = require('webpack')
module.exports = {
entry: {
react: ['react', 'react-dom']
},
mode: 'development',
output: {
path: path.resolve(__dirname, './dll'),
filename: "[name].dll.js",
library: 'react'
},
plugins: [
new DllPlugin({
// 生成一个 manifest.json 文件,并指定位置
path: path.join(__dirname, './dll', '[name]-manifest.json'),
name: 'react' // name 要和 labray 名称一致
})
]
}
package.json 中新增命令
scripts: {
"dev:dll": "webpack --config ./webpack.dll.config.js",
}
运行 npm run dev:dll 后会在根目录下生成一个 dll 文件
在 webpack.config.js 中使用
const webpack = require('webpack')
{
plugins: [
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, './dll/react-manifest.json')
}),
]
}
到目前为止还需要在 index.html 中手动引入生成的 react.dll.js 文件才算配置完成,这里的话我们借助插件 AddAssetHtmlWebpackPlugin 可以帮你自动添加 js 到 html 中
安装
npm i add-asset-html-webpack-plugin -D
webpack.config.js 中使用
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
{
plugins: [
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, './dll/react.dll.js')
})
]
}
六、环境区分
image到目前为止 我们 webpack.config.js 文件已经非常庞大了。很多生产环境和开发环境的配置也是冲突的
这里就要区分环境了,这里我们需要安装几个包帮助我们来合并 webpack 配置对象,通过命令传参 等
// 合并 webpack 配置对象
npm i webpack-merge -D
// 在执行命令的时候传参
npm i cross-env -D
根目录下新建 webpack.dev.config.js / webpack.pro.config.js,将 webpack.config.js 改名为 webpack.base.config.js
webpack.base.config.js
// webpack 默认配置
const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: path.resolve(__dirname, './src/react.js'),
output: {
path: path.resolve(__dirname, './dist'),
filename: 'main_[hash:8].js'
},
module: {
rules: [
{
test: /\.css|less$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
// 您可以在此处指定publicPath
// 默认情况下,它在webpackOptions.output中使用publicPath
publicPath: '../'
}
}, 'css-loader', 'postcss-loader', 'less-loader'
],
// 这里会直接到 src 文件下找 less/css 文件进行编译,这里是项目优化的一个小技巧
include: path.resolve(__dirname, './src')
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {},
},
],
},
{
test: /\.js$/,
loader: 'babel-loader'
},
]
},
plugins: [
// 复制一个 html 并将最后打包好的资源在 html 中引入
new htmlWebpackPlugin({
// 页面title 需要搭配 ejs 使用
title: "webpack-react",
// html 模板路径
template: "./index.html",
// 输出文件名称
filename: "index.html",
minify: {
// 压缩HTML⽂件
removeComments: true, // 移除HTML中的注释
collapseWhitespace: true, // 删除空⽩符与换⾏符
minifyCSS: true // 压缩内联css
}
}),
// 每次部署时清空 dist 目录
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: "css/[name]_[contenthash:6].css",
})
],
resolve: {
// 规定在那里寻找第三方模块
modules: [path.resolve(__dirname, './node_modules')],
// 别名
alias: {
react: path.resolve(__dirname, './node_modules/react/umd/react.production.min.js'),
'react-dom': path.resolve(__dirname, './node_modules/react-dom/umd/react-dom.production.min.js'),
'@': path.resolve(__dirname, './src')
},
// 自动补齐后缀名
extensions: ['.js', '.jsx']
},
}
修改 webpack.dev.config.js
// webpack 默认配置
const path = require('path');
const webpack = require("webpack");
// 引入js到 html 文件中
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const merge = require('webpack-merge');
const webpackBase = require('./webpack.base.config');
// merge 用法和 Object.assign 类似
module.exports = merge(webpackBase, {
mode: 'development',
plugins: [
// 启用模块热替换(HMR - Hot Module Replacement)
new webpack.HotModuleReplacementPlugin(),
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, './dll/react-manifest.json')
}),
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, './dll/react.dll.js')
})
],
devtool: 'cheap-module-eval-source-map',
// // 启动项目
devServer: {
contentBase: './dist',
open: true,
port: 8081,
hot: true,
hotOnly: true
},
})
webpack.pro.config.js
// webpack 默认配置
const path = require('path');
// 压缩 Css 文件
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
// 用于处理多路径文件,使用purifycss的时候要用到glob.sync方法。
const glob = require('glob-all')
// Css tree shanking 摇树
const purifyCssWebpack = require('purifycss-webpack')
module.exports = {
mode: 'production',
plugins: [
// 压缩css文件
new OptimizeCssAssetsWebpackPlugin({
cssProcessor: require('cssnano'),
cssProcessorPluginOptions: {
// 去掉注释
preset: ["default", { discardComments: { removeAll: true } }]
}
}),
new purifyCssWebpack({
paths: glob.sync([
path.resolve(__dirname, './src/*html'),
path.resolve(__dirname, './src/*js')
])
}),
],
optimization: {
// js 开启 tree shanking
usedExports: true,
splitChunks: {
chunks: "all", // 代码分隔 公共代码分离出来
name: true,
cacheGroups: {
react: {
test: /[\\/]react|react-dom[\\/]/,
name: 'react'
},
lodash: {
test: /[\\/]lodash[\\/]/,
name: 'lodash'
}
}
}
}
}
修改 package.json 文件
"scripts": {
"dev": "webpack ",
"dev:dll": "webpack --config ./webpack.dll.config.js",
"server": "webpack-dev-server --config ./webpack.dev.config.js",
// 这里通过 cross-env 传了一个 NODE_ENV 变量 可以通过 process.env.NODE_ENV 获取变量的值
"build": "cross-env NODE_ENV=production webpack --config ./webpack.pro.config.js"
},
七、happypack
webpack 在 node 环境下运行也是单线程,所有操作都要等待上一步完成。这里可以借助 happypack 的多线程的功能,给 webpack 开个挂,实现多进程打包
由于HappyPack 对file-loader、url-loader支持的不友好,所以不建议对该loader使用。
安装
npm i happypack -D
webpack.base.config.js 中配置一下
const Happypack = require('happypack');
//构造出一个共享进程池,在进程池中包含4个子进程
const happyThreadPool = Happypack.ThreadPool({
size: 4
})
module: {
rules: [
{
test: /\.js$/,
use: 'Happypack/loader?id=happypackJs',
include: path.resolve(__dirname, './src')
}
]
},
plugins: [
new Happypack({
// 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
id: 'happypackJs',
// 如何处理 .js 文件,用法和 Loader 配置中一样
use: ['babel-loader'],
//使用共享进程池中的自进程去处理任务
threadPool: happyThreadPool,
//是否允许happypack输出日志,默认true
verbose: true
}),
]
总结
到目前为止 webpack 篇章就算结束了,后续的话我将来写一下 Vue 源码实现相关的文章,你的点赞就是我的动力,请求一个点赞+关注。感谢。
网友评论