前面已经实现了一个简单webpack配置,接下来须要在前面的基础上对webpack.config.js
进行拆分。javascript
项目开发时,咱们须要用webpack-dev-server
启动开发服务器,当咱们修改文件时,它能自动从新打包项目并刷新页面。css
项目打包上线时,咱们但愿webpack能进行更多的处理来优化打包后的代码。html
针对不一样的需求,咱们须要将配置文件拆分为webpack.common.js
webpack.dev.js
webpack.prod.js
。前端
项目代码 Github 仓库java
webpack.common.js
这个文件是共用的配置。node
const path = require("path");
module.exports = {
// 入口文件改成 .jsx文件
entry: "./src/index.jsx",
resolve: {
extensions: [".js", ".json", ".jsx"]
},
module: {
rules: [
{
test: /\.jsx?$/,
// include告诉webpack只对src下的
// js、jsx文件进行babel转译
// 加快webpack的打包速度
include: path.resolve(__dirname, "src"),
use: "babel-loader"
}
]
}
};
复制代码
webpack.dev.js
咱们须要使用webpack-merge
将前面配好的webpack.comm.js
合并进来。并且须要webpack-dev-server
来启动开发服务器。react
安装:webpack
webpack-merge
webpack-dev-server
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const merge = require("webpack-merge");
const common = require("./webpack.common");
// 使用webpack-merge将webpack.common.js合并进来
module.exports = merge(common, {
// 设置为开发(development)模式
mode: "development",
// 设置source map,方便debugger
devtool: "inline-source-map",
output: {
filename: "main.js",
path: path.resolve(__dirname, "dist"),
publicPath: "/"
},
devServer: {
// 单页应用的前端路由使用history模式时,这个配置很重要
// webpack-dev-server服务器接受的请求路径没有匹配的资源时
// 他会返回index.html而不是404页面
historyApiFallback: true
},
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
},
{
test: /\.(png|jpg|jpeg|svg|gif)$/,
use: "file-loader"
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/template.html",
favicon: "./src/assets/favicon-32x32-next.png"
})
]
});
复制代码
webpack.prod.js
打包生产环境使用的代码须要很是多的优化和处理,因此这个文件的配置会很是复杂。git
main.js
内抽离到单独的.css
文件main.js
内抽离出来source-map
替代inline-source-map
dist
目录前面的webpack.dev.js
只是简单的使用了css-loader
和style-loader
。github
css-loader
将项目导入的css样式转为js模块,打包到main.js
内。
style-loader
在main.js
内提供了一个能将css动态插入到html内的方法。
当用户打开页面时,会先加载html,而后加载main.js
,最后运行js脚本将样式插入到style
标签内。
这样有多个缺点:
main.js
内,增长了它的文件大小,并且也不方便对css作缓存main.js
脚本运行,并插入到html时才会有效果。这个空档期虽然很短,但它会形成界面闪烁。优势:
因此style-loader
与css-loader
的组合仅适用于开发。咱们须要使用mini-css-extract
插件,并用该插件提供的loader来替代style-loader
。
// ...
plugins:[
// 配置mini-css-extract插件
new MiniCssExtractPlugin({
// 设置抽取出来的css名字
filename: "[name].[contentHash].css",
chunkFilename: "[id].[contentHash].css"
}),
// ...
],
// ...
复制代码
// ...
module:{
rules:[
{
test: /\.css$/,
// 使用MiniCssExtractPlugin.loader替代style-loader
use: [MiniCssExtractPlugin.loader, "css-loader"]
},
// ...
]
},
// ...
复制代码
缓存是前端页面性能优化的重点。咱们但愿浏览器能长久缓存资源,同时又能在第一时间获取更新后的资源。
具体思路是:后端不对index.html
作任何缓存处理,对css、js、图片等资源作持久缓存。将output.filename
配置为"main.[contentHash].js"
,这样打包后的main.js
中间会加上一段contentHash
。contentHash
是根据打包文件内容产生的,内容改变它才会发生改变。发布时,因为哈希值不一样,服务器能同时保存着不一样哈希版本的资源。这样保证了发布过程当中,用户仍然可以访问到旧资源,而且新用户会访问到新资源。
加上contentHash
后打包文件名变成main.xxxxxx.js
,其中xxxxxx
表明一串很长的哈希值。
可是,如今咱们的业务代码、引用的第三方库,还有webpack生成的runtime都被捆绑打包到了main.xxxxxx.js
内。第三方库和webpack的runtime变更的频率很是低,因此咱们不但愿每次业务代码的改动致使用户得连同它们一块儿从新下载一遍。所以咱们须要将它们从main.xxxxxx.js
内抽离出来。
还有一点须要注意,webpack之前的版本有个小小的问题。在打包文件内容没发生变化的状况下contentHash
任然会发生改变。此时须要使用webpack.HashedModuleIdsPlugin
插件来替代默认的哈希生成。虽然webpack4修复了这个问题,可是官方文档仍是推荐咱们使用webpack.HashedModuleIdsPlugin
插件。
前面已经说明了为何要抽离他们。
之前的版本使用commons-chunk-plugin
插件来抽离第三方库,webpack 4经过配置optimization.splitChunks
来抽取。内部其实使用了split-chunks-plugin
插件。
将optimization.runtimeChunk
选项配置为single
,能够将webpack runtime抽离到单文件中。
// ...
optimization: {
// 抽离webpack runtime到单文件
runtimeChunk: "single",
splitChunks: {
chunks: "all",
// 最大初始请求数量
maxInitialRequests: Infinity,
// 抽离体积大于80kb的chunk
minSize: 80 * 1024,
// 抽离被多个入口引用次数大于等于1的chunk
minChunks: 1,
cacheGroups: {
// 抽离node_modules下面的第三方库
vendor: {
test: /[\\/]node_modules[\\/]/,
// 从模块的路径地址中得到库的名称
name: function(module, chunks, chacheGroupKey) {
const packageName = module.context.match(
/[\\/]node_modules[\\/](.*?)([\\/]|$)/
)[1];
return `vendor_${packageName.replace("@", "")}`;
}
}
}
},
// ...
},
// ...
复制代码
打包后的js、css、html会有注释、空格、换行,开启代码压缩能够大幅度减小资源的体积。
在mode
设为"production"
时,会默认使用terser-webpack-plugin
插件对js进行压缩。咱们还要开启html与css的压缩,因此要重写optimization.minimizer
选项。
// ...
optimization: {
minimizer: [
// 压缩css
new OptimizeCssAssetsWebpackPlugin(),
// 压缩js,记得sourceMap设为true
new TerserWebpackPlugin({ sourceMap: true }),
// 该插件还能对html进行压缩
new HtmlWebpackPlugin({
template: "./src/template.html",
favicon: "./src/assets/favicon-32x32-next.png",
minify: {
// 折叠空白符(去除换行符和空格)
collapseWhitespace: true,
// 移除注释
removeComments: true,
// 移除属性上没必要要的引号
removeAttributeQuotes: true
}
})
],
// ...
}
// ...
复制代码
source-map
替代inline-source-map
发生bug时,咱们很难经过打包后的代码找出错误的源头。因此咱们须要source map将代码映射为原来咱们手写时候的样子。
前面的webpack.dev.js
内使用的是inline-source-map
。它的缺点是将map内敛到了代码内,这样用户会连同资源将map一块儿下载。
因此咱们使用source-map
,它会给打包后的每一个js单独生成.map
文件。
懒加载也叫按需加载。咱们当前打包的全部js会在页面加载过程当中被加载运行。可是大多数状况下,用户并不会访问应用的全部页面与功能。咱们能够将每一个页面的代码或一些不常使用的功能模块作成按需加载,这样能够大大减少用户初次访问时所要加载的资源大小。
懒加载是webpack4默认支持的,不须要任何配置。前端人员须要在开发时使用dynamic import按需引入模块。webpack会自动将dynamic import引入的模块单独打包为一个chunk(注意:dynamic import语法上须要babel插件的支持,会在下一章节提到该插件)。
webpack官网提供的例子:
// print.js
console.log('The print.js module has loaded! See the network tab in dev tools...');
export default () => {
console.log('Button Clicked: Here\'s "some text"!');
};
复制代码
// src/index.js
// ...
button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => {
var print = module.default;
print();
});
// ...
复制代码
经过点击按钮,触发加载print
模块。/* webpackChunkName: "print" */
这个注释告诉webpack该模块打包成的chunk名字叫print
。
dist
目录使用clean-webpack-plugin
在打包前清空dist
目录。
webpack.prod.js
的完整配置安装:
mini-css-extract-plugin
clean-webpack-plugin
terser-webpack-plugin
optimize-css-assets-webpack-plugin
url-loader
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const merge = require("webpack-merge");
const common = require("./webpack.common");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CleanWebpackPlugin = require("clean-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");
const webpack = require("webpack");
module.exports = merge(common, {
// 设置为生产(production)模式
mode: "production",
// 在生产环境中使用"source-map"而不是"inline-source-map"
devtool: "source-map",
output: {
// 这里添加contentHash
// 因为咱们的entry中没有配置入口的名称
// webpack会默认取名为main
// 所以这里的配置会生成"main.xxxxxx.js"
filename: "[name].[contentHash].js",
// 经过splitChunks抽离的js文件名格式
chunkFilename: "[name].[contentHash].chunk.js",
path: path.resolve(__dirname, "dist"),
publicPath: "/"
},
module: {
rules: [
{
test: /\.css$/,
// 这里使用MiniCssExtractPlugin.loader替代style-loader
use: [MiniCssExtractPlugin.loader, "css-loader"]
},
{
test: /\.(png|jpg|jpeg|svg|gif)$/,
use: {
// 这里使用url-loader替代file-loader
loader: "url-loader",
options: {
// 当图片小于8kb时,url-loader会将图片转为base64
// 这样能够减小http请求的数量
// 若是大于8kb的话,url-loader会将图片交给file-loader处理
// 因此url-loader须要依赖file-loader
limit: 1024 * 8,
name: "img/[name].[hash:8].[ext]"
}
}
}
]
},
optimization: {
// 抽离webpack runtime到单文件
runtimeChunk: "single",
// 压缩器
minimizer: [
// 压缩css
new OptimizeCssAssetsWebpackPlugin(),
// 压缩js,记得将sourceMap设为true
// 不然会没法生成source map
new TerserWebpackPlugin({ sourceMap: true }),
// 该插件还能压缩html
new HtmlWebpackPlugin({
template: "./src/template.html",
favicon: "./src/assets/favicon-32x32-next.png",
minify: {
// 折叠空白符
collapseWhitespace: true,
// 移除注释
removeComments: true,
// 移除属性多余的引号
removeAttributeQuotes: true
}
})
],
splitChunks: {
chunks: "all",
// 最大初始请求数
maxInitialRequests: Infinity,
// 80kb以上的chunk抽离为单独的js文件
// 配合上面的 maxInitialRequests: Infinity
// 小于80kb的全部chunk会被打包一块儿
// 这样能够减小初始请求数
// 你们能够根据本身的状况设置
minSize: 80 * 1024,
// 抽离多入口引用次数1以上的chunk
minChunks: 1,
cacheGroups: {
// 抽离node_modules内的第三方库
vendor: {
test: /[\\/]node_modules[\\/]/,
// 根据路径得到第三方库的名称
// 并将抽离的chunk以"vendor_thirdPartyLibrary"格式命名
name: function(module, chunks, chacheGroupKey) {
const packageName = module.context.match(
/[\\/]node_modules[\\/](.*?)([\\/]|$)/
)[1];
return `vendor_${packageName.replace("@", "")}`;
}
}
}
}
},
plugins: [
// 每次打包前,先清除输出目录
new CleanWebpackPlugin(),
// 抽离css
new MiniCssExtractPlugin({
filename: "[name].[contentHash].css",
chunkFilename: "[id].[contentHash].css"
}),
// 确保在文件没发生改变时,contentHash也不会变化
new webpack.HashedModuleIdsPlugin()
]
});
复制代码
// ...
"scripts": {
"start": "webpack-dev-server --config webpack.dev.js",
"build": "webpack --config webpack.prod.js"
},
// ...
复制代码
基本配置完成,能够安装react-router redux进行单页应用开发了
npm i react-router-dom redux react-redux redux-thunk
复制代码
下章节内容:添加babel插件支持decorator、类属性与dynamic import;添加sass预处理;添加postcss Autoprefixer自动补充浏览器厂商前缀;使用.browserslistrc
配置须要兼容的浏览器范围;添加Prettier ESLint来规范与格式化代码;