1. 前言
📖回顾:
上一篇文章webpack开发环境配置从文件类型的角度出发,简单介绍了样式、html、图片等资源要如何打包,还简单配置了一下开发服务器devServer,没印象的话可以回去复习一下哦~
与上一篇文章不同的是,本文会从文件内容的角度出发,介绍样式、html、js的文件提取、兼容性处理和压缩。可能不会深入介绍每个配置项,有需要可以挨个去查阅官方文档~
另外,从篇幅考虑,只会列出必要的代码,完整的代码在这里
2. css文件
2.1. 提取css成单独文件
使用的plugin:mini-css-extract-plugin
思路
首先,在上一篇开发环境中,webpack的配置是这样的:
- css-loader:将css文件转换成commonjs模块加载到js中,css代码被转换成了样式字符串。(转换后得到的commonjs模块可以理解为:用js给元素动态添加样式的那种代码);
- style-loader:创建style标签,将css-loader生成的样式资源插入进去,添加到head中,使样式生效。
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader', // 创建style标签,将'css-loader'整合到js中的样式字符串放到style标签中。
'css-loader' // 将css文件转换成commonjs模块加载到js中,css代码被转换成了样式字符串。
]
}
]
},
plugins: [],
mode: 'development'
}
现在,我们想提取css成一个文件。显然,不再需要style-loader
将样式代码塞进<style></style>
标签了。需要的是另外一个loader或plugin,用于生成一个单独的文件,并将样式代码塞进去。
webpack配置
const { resolve } = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: [
// 'style-loader', // 创建style标签,将'css-loader'整合到js中的样式放到style标签中
// 用这个loader取代style-loader,因为想将css单独输出为一个文件
MiniCssExtractPlugin.loader, // 作用:提取js中的css成单独文件
'css-loader' // 将css文件整合到js中
]
}
]
},
plugins: [
new HTMLWebpackPlugin({
template: './src/index.html'
}),
new MiniCssExtractPlugin({
// 对输出的css文件重命名
filename: 'css/built.css'
})
],
mode: 'development'
}
2.2. css兼容性处理
使用的loader:postcss-loader
需要下载两个包:postcss-loader
、postcss-preset-env
-
postcss-loader
:根据node环境变量生成对应环境的兼容性代码;- 设置环境变量:
process.env.NODE_ENV = 'development'
; - 如果不手动设置node环境变量,默认为"production"。
- 设置环境变量:
-
postcss-preset-env
:帮助postcss-loader
找到package.json中browserslist的配置,大概长下边这样:"browserslist": { // 开发环境,这里对应的是node的环境变量(非webpack.config.js配置的mode):process.env.NODE_ENV = development "development": [ "last 1 chrome version", //chrome最新的一个版本 "last 1 firefox version", "last 1 safari version" ], "production": [ ">0.2%", // 兼容市面上(使用人数?待查)最靠前的99.8%的浏览器 "not dead", // 兼容非死亡的浏览器 "not op_mini all" // 兼容非open mini浏览器 ] }
思路
回顾一下2.1中css文件的打包流程:先用 css-loader
将样式加载到js中;再用MiniCssExtractPlugin.loader
将样式打包进单独的文件。
进行兼容性处理的话,我们想一下,相对简单的处理顺序可能有两种:在css-loader
前或MiniCssExtractPlugin.loader
后。
这里选择在css-loader
前,为什么是这个顺序我现在还没有搞清楚,知道的伙伴可以分享一下🙏🙏
webpack配置
1)先要在package.json中配置字段browserslist
:
"name": "webpack-study",
"version": "1.0.0",
"description": "",
"main": "index.js",
"browserslist": {
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
},
}
2)webpack配置:
const { resolve } = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// 配置node的环境变量
process.env.NODE_ENV = 'development';
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
ident: 'post-css',
plugins: [
'postcss-preset-env'
]
}
}
}
]
}
]
},
plugins: [
new HTMLWebpackPlugin({
template: './src/index.html'
}),
new MiniCssExtractPlugin({
filename: 'css/built.css'
})
],
mode: 'development'
}
2.3. 压缩css
使用的plugin:optimize-css-assets-webpack-plugin
webpack配置
const { resolve } = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const MiniCSSExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
// 配置node的环境变量 - css兼容性处理时使用
// process.env.NODE_ENV = 'development';
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCSSExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
ident: 'postcss',
plugins: [
'postcss-preset-env'
]
}
}
}
]
}
]
},
plugins: [
new HTMLWebpackPlugin({
template: './src/index.html'
}),
new MiniCSSExtractPlugin({
filename: 'css/built.css'
}),
// 压缩css
new OptimizeCSSAssetsWebpackPlugin()
],
mode: 'development'
}
3. JS文件
3.1. JS语法检查eslint
使用的loader:eslint-loader
需要下载:
eslint-loader
和eslint
:eslint-loader是基于eslint的(可以类比理解为:js与nodejs环境的关系);- 可以在package.json中增加
eslintConfig
字段,配置eslint。比如可以在eslintConfig中设置检查规则:"eslintConfig": { "extends": "airbnb-base" }
。
- 可以在package.json中增加
eslint-config-airbnb-base
和eslint-plugin-import
:因本文会设置airbnb-base
的检查规则来进行语法检查,而到eslint-config-airbnb-base
官网可以查到还需下载eslint-plugin-import
。
webpack配置
1)先要在package.json中配置字段eslintConfig
:
{
"name": "webpack-study",
"version": "1.0.0",
"description": "",
"main": "index.js",
"eslintConfig": {
"extends": "airbnb-base",
"env": {
"browser": true
}
}
}
2)webpack配置:
const { resolve } = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build'),
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'eslint-loader',
options: {
// 自动修复eslint的错误
fix: true
}
}
],
},
plugins: [
new HTMLWebpackPlugin({
template: './src/index.html',
}),
],
mode: 'development',
};
3.2. JS兼容性处理
1)什么是JS兼容性处理?
其实就是把ES6及以上(后边简写为ES6+)新增的语法、API处理成ES5及以下的版本,解决某些浏览器(ie)上的兼容性报错问题。
2)Babel简介:
Babel 是一个工具链,主要用于在当前和旧的浏览器或环境中,将ES6+代码转换为JavaScript向后兼容版本的代码。
3)polyfill是啥?
⁉️:不知道大家之前有没有过这种疑问:只知道babel是处理ES6+兼容的,polyfill分别是干嘛的😵?
🅰️:其实,ES6+语法和新增的API需要分开进行处理,polyfill是针对新增API的~(比如:箭头函数是新增语法,Promise是新增的API)
4)请大家跟我一起看一段代码(下面JS兼容性处理相关内容将以这个作为例子):
// 新语法
const add = (x, y) => {
return x + y;
};
console.log(add(2, 5));
// 新API
const promise = new Promise(resolve => {
setTimeout(() => {
console.log('定时器执行完了~');
resolve();
}, 1000);
});
console.log(promise);
上面代码如果不做任何兼容性处理,运行结果如下: ie无法识别ES6+的内容
3.2.1. 处理语法
1)下载
- 基础核心包:
babel-loader
和@babel/core
; - 处理语法:
@babel/preset-env
。
2)webpack配置
// 以下是单个loader配置
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
presets: [
[
'@babel/preset-env'
]
]
}
3)运行结果
IE仍然无法识别ES6+新增的API
3.2.2. 处理语法+API(非按需加载)
1)下载
- 基础核心包:
babel-loader
和@babel/core
; - 处理语法:
@babel/preset-env
; - 处理API(直接在js中引入):
@babel/polyfill
。
2)配置
webpack.config.js:
// 以下是单个loader配置
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
presets: [
[
'@babel/preset-env'
]
]
}
在上面的例子最顶部加import '@babel/polyfill';
:
import '@babel/polyfill';
const add = (x, y) => {
return x + y;
};
console.log(add(2, 5));
const promise = new Promise(resolve => {
setTimeout(() => {
console.log('定时器执行完了~');
resolve();
}, 1000);
});
console.log(promise);
3)运行结果
4) 缺点
我们来看看打包后的代码:
可以看出,虽然解决了JS兼容性问题,但这种方法将ES6+新增的所有API都打包进来了,造成了代码的冗余。事实上我们只需要箭头函数和Promise的部分==
3.2.3. 处理语法+API(按需加载)
1)下载
- 基础核心包:
babel-loader
和@babel/core
; - 处理语法和API:
@babel/preset-env
+ 配置;- 配置基于:
core-js
。
- 配置基于:
2)webpack配置
注意:不需要在js中import @babel/polyfill
// 以下是单个loader配置
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
// 预设:指示babel做怎么样的兼容性处理
presets: [
[
'@babel/preset-env',
{
/** https://babeljs.io/docs/en/babel-preset-env#usebuiltins
* useBuiltIns:配置@babel/preset-env如何处理polyfills,取“usage”,“entry”,“false”之一,默认为“false”
* - 当使用usage或entry选项时,@babel/preset-env将添加对core-js模块的直接引用,类似import(或require)。这意味着core-js将相对于文件本身进行解析,并且按需引入。
*/
useBuiltIns: 'usage', // 按需加载
corejs: { version: 3 }, // 指定corejs的版本
targets: { chrome: '60', firefox: '60', ie: '9', safari: '10', edge: '17' } // 指定兼容到浏览器哪个版本
}
]
]
}
}
3)运行结果
3.3. 压缩JS
非常简单,只需要把mode设置为“production”。
原因:“production” mode下,webpack会自动启用TerserWebpackPlugin/UglifyJsPlugin
来压缩。
4. HTML文件
HTML文件不像CSS和JS,不需要做兼容性处理。
4.1. 压缩HTML
使用的plugin:html-webpack-plugin
+ 配置
webpack配置
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
plugins: [
// html压缩只需配置html-webpack-plugin
new HTMLWebpackPlugin({
template: './src/index.html',
minify: {
removeComments: true, // 移除注释
collapseWhitespace: true // 移除空格
// 更多配置可以自行探索😝~
}
})
],
mode: 'production'
}
5. 思考和总结
5.1. 捋一捋
🎯有部分资源的打包放在了上一次webpack开发环境配置中。
从文件类型出发,一起来捋捋👉
- 先是样式(css/less/sass):
- css文件:
postcss-loader
处理css兼容性(还需下载postcss-preset-env
,在package.json中配置browserslint
字段);css-loader
将css文件转换成commonjs模块加载到js中;mini-css-extract-plugin
将js中的css模块提取成单独的一个文件;optimize-css-assets-webpack-plugin
压缩css;
- less文件:
- 在css文件的基础上多了
less-loader
(需在压缩前执行,还需下载less
);
- 在css文件的基础上多了
- sass文件:
- 在css文件的基础上多了
sass-loader
(需在压缩前执行,还需下载node-sass
);
- 在css文件的基础上多了
- css文件:
- 再是js文件:
eslint-loader
做语法检查(还需下载eslint
、eslint-config-airbnb-base
、eslint-plugin-import eslint
,在package.json中配置eslintConfig
字段)babel-loader
处理js兼容性:babel-loader
+@babel/core
+@babel/preset-env
:只能处理ES6+语法问题;babel-loader
+@babel/core
+@babel/preset-env
+@babel/polyfill
:以非按需加载的方式处理js全部兼容性问题;babel-loader
+@babel/core
+@babel/preset-env
+ 配置:以按需加载的方式处理js全部兼容性问题(推荐);
- 将mode设置为
production
,自动压缩js代码;
- 然后是html文件:
html-webpack-plugin
创建一个空的html并自动引入所有webpack打包输出的资源;- 再对其进一步配置,做到压缩html代码。
- 还有图片资源:
url-loader
处理css中的图片资源(img标签除外)(还需下载file-loader
);- 针对img标签:
html-loader
处理html文件的img图片,从而能被url-loader处理;
- 最后其他资源:
file-loader
5.2. 总的webpack配置
跟着捋出来的思路,可以写一下~
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
process.env.NODE_ENV = 'production';
const commonCssLoader = [
MiniCssExtractPlugin.loader,
// 'style-loader', // 直接将css放到style标签里
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
ident: 'post-css',
plugins: [ 'postcss-preset-env' ] // 还需要在package.json中配置browserslist
}
}
}
];
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: [...commonCssLoader]
},
{
test: /\.less$/,
use: [ ...commonCssLoader, 'less-loader' ]
},
/**
* 注意:正常来讲,一个文件只能由一个loader处理。
* 当一个文件要被多个loader处理,需要指定loader执行的先后顺序。
* 这里:应先执行eslint-loader,再执行babel-loader
*/
{
// 需要在package.json中配置eslintConfig
test: /\.js$/,
exclude: /node_modules/,
enforce: 'pre', // 优先执行
loader: 'eslint-loader',
options: {
fix: true
}
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3 },
targets: {
chrome: '60',
firefox: '50',
ie: '9'
}
}
]
]
}
},
{
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
outputPath: 'images',
esModule: false
}
},
{
test: /\.html$/,
loader: 'html-loader',
options: {
esModule: false
}
},
{
exclude: /\.(js|html|css|less|jpg|png|gif)$/,
loader: 'file-loader',
options: {
outputPath: 'assets'
}
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/built.css'
}),
new OptimizeCssAssetsWebpackPlugin(),
new HTMLWebpackPlugin({
template: './src/index.html',
minify: {
removeComments: true,
collapseWhitespace: true
}
})
],
mode: 'production'
}