前言
趁工作之余玩了下webpack,现在都出到4.8版本了,不得不感叹前端技术版本更新真的很快!如果你还不会webpack,那你绝不是一个合格的前端开发者。
what is webpack?
webpack可以看做一个模块化打包机,分析项目结构,处理模块化依赖,转换成为浏览器可运行的代码。
初始化项目
// 这里不做过多描述
npm init
新建src文件夹,并新建index.js,随便写几行js。
安装webpack
webpack4.0以上抽离出了webpack-cli,所以我们需要下载2个依赖。
npm install webpack webpack-cli -D
- npm i -D 是 npm install --save-dev 的简写,是指安装模块并保存到 package.json 的 devDependencies中,主要在开发环境中的依赖包。
webpack4可以支持0配置打包,不过在实际开发中,0配置是根本无法满足需求的。不过我们看看到底0配置了什么?
// node v8.2版本以上会有npx
npx webpack // 不设置mode的情况下 打包出来的文件自动压缩
npx webpack --mode development // 设置mode为开发模式,打包后的文件不被压缩
当执行npx webpack命令时,webpack会自动寻找项目中src目录下的index.js文件,然后进行打包,生成一个dist目录并生成一个打包好的main.js文件
- 启动devServer需要安装一下webpack-dev-server
npm i webpack-dev-server -D
定制化配置webpack
webpack需要在项目根目录下创建一个webpack.config.js来导出webpack的配置,配置多样化,可以自行定制。
// webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js', // 入口文件
output: {
filename: 'bundle.js', // 打包后的文件名称
path: path.resolve('dist') // 打包后的目录,必须是绝对路径
}
}
-
entry
代表入口 -
output
输出文件配置 -
path
输出的文件地址 -
filename
输出文件的名字
接下来我们执行命令webpack
,此时我们可以看到目录下多出了一个dist文件,并生成了bundle.js。
实际开发中我们打包编译的时候一般都通过npm执行的命令,所以我们找到package.json里的执行脚本去配置一下命令,这里如下图所示:
npm run build
执行打包命令
rimraf dist
每次打包前 删除dist文件
set NODE_ENV=build
和set NODE_ENV=dev
设置环境变量来区分开发环境和生成环境
--open
自动打开默认浏览器
npm run dev
开发环境下打包的文件,但是devServer帮我们把文件放到内存中,所以并不会输出打包后的dist文件夹
多文件入口
- 实际开发中基本都是spa单页面,但是我们这里还是简单说一下多入口配置
const path = require('path');
module.exports = {
// 1.写成数组的方式就可以打出多入口文件,但是打包后会合成一个
// entry: ['./src/index.js', './src/index2.js'],
entry: {
index: './src/index.js',
index2: './src/index2.js'
},
output: {
// 1. filename: 'bundle.js',
// 2. [name]就可以将出口文件名和入口文件名一一对应
filename: '[name].[hash:6].js', // 打包后会生成index.js和index2.js文件
path: path.resolve('dist')
}
}
hash
打包文件后自动加上hash值,避免缓存,取前6位。
执行npm run build
后,dist目录下生成打包好的两个js文件:index.js 和 index2.js
压缩js
实际开发中,我们的js一般都很大,所以需要压缩;需要用到uglifyjs-webpack-plugin
插件
npm i uglifyjs-webpack-plugin -D
接下来配置下:
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
plugins: [
new UglifyJsPlugin()
]
}
这样就实现了js的压缩
HTML文件的发布
js文件都打包好了,但是我们不能每次都手动创建一个index.html去加载你的js,这样肯定是不切实际的。
这时候需要一个html-webpack-plugin的插件来帮助我们
npm i html-webpack-plugin -D
webpack配置插件都需要在plugins中完成,ok 我们配置下:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); //打包html
module.exports = {
// 1.写成数组的方式就可以打出多入口文件,但是打包后会合成一个
// entry: ['./src/index.js', './src/index2.js'],
entry: {
index: './src/index.js',
index2: './src/index2.js'
},
output: {
// 1. filename: 'bundle.js',
// 2. [name]就可以将出口文件名和入口文件名一一对应
filename: '[name].[hash:6].js', // 打包后会生成index.js和index2.js文件
path: path.resolve('dist')
},
plugins: [
// 通过new一下这个类来使用插件
new HtmlWebpackPlugin({
template: './src/index.html',
hash: true, // 会在打包好的bundle.js后面加上hash串
minify: {
minifyCSS: true, // 压缩 HTML 中出现的 CSS 代码
minifyJS: true, // 压缩 HTML 中出现的 JS 代码
removeAttributeQuotes: true
},
}),
]
}
-
template
在src目录下创建一个index.html页面当做模板来用 -
hash
会在打包好的bundle.js后面加上hash串 -
minify
是对html文件进行压缩 -
removeAttributeQuotes
去掉属性的双引号 -
minifyCSS
压缩 HTML 中出现的 CSS 代码 -
minifyCSS
压缩 HTML 中出现的 JS代码
ok 执行npm run build
,此时dist目录下自动生成index.html,并自动引入了js文件
CSS文件发布
在webpack中打包css、less需要单独的loader去解析
npm i style-loader css-loader less less-loader -D
接下来我们配置下:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); //打包html
module.exports = {
// 1.写成数组的方式就可以打出多入口文件,但是打包后会合成一个
// entry: ['./src/index.js', './src/index2.js'],
entry: {
index: './src/index.js',
index2: './src/index2.js'
},
output: {
// 1. filename: 'bundle.js',
// 2. [name]就可以将出口文件名和入口文件名一一对应
filename: '[name].[hash:6].js', // 打包后会生成index.js和index2.js文件
path: path.resolve('dist')
},
module: {
rules: [
{
test: /\.css/,
exclude: /node_modules/, // 取消匹配node_modules里面的文件
use: ['style-loader', 'css-loader']
},
{
test: /\.less$/,
exclude: /node_modules/, // 取消匹配node_modules里面的文件
use: ['style-loader', 'css-loader', 'less-loader']
},
]
},
plugins: [
// 通过new一下这个类来使用插件
new HtmlWebpackPlugin({
template: './src/index.html',
hash: true, // 会在打包好的bundle.js后面加上hash串
minify: {
minifyCSS: true, // 压缩 HTML 中出现的 CSS 代码
minifyJS: true, // 压缩 HTML 中出现的 JS 代码
removeAttributeQuotes: true
},
}),
]
}
-
test
正则表达式,匹配文件名 -
use
数组,里面放需要执行的loader,倒序执行,从右至左。(小坑:style-loader最后执行) -
exclude
取消匹配node_modules里面的文件
ok 执行npm run build
,此时我们发现并没有看到css文件生成,这是为什么呢?因为webpack默认把我们的css合并到bundle.js中了,如下图:
实际开发中,我们一般都吧css 和 js分离。
拆分CSS
在webpack3.x版本中可以用extract-text-webpack-plugin
插件进行拆分,但这里不做过多描述,如果感兴趣的同学可以去官网查看文档。因为webpack4.x新出了mini-css-extract-plugin
插件, 我们就用最新的。让我们安装一下
npm i mini-css-extract-plugin -D
接下来我们加入配置:(为了方便观看,只显示当前配置项)
const path = require('path');
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 提取出来css
module.exports = {
module: {
rules: [
{
test: /\.css/,
exclude: /node_modules/, // 取消匹配node_modules里面的文件
use: ['style-loader', MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.less$/,
exclude: /node_modules/, // 取消匹配node_modules里面的文件
use: ['style-loader', MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
},
]
},
plugins: [
// 把css从bundle.js中分离出来
new MiniCssExtractPlugin({
filename: "static/css/[name].[chunkhash:8].css",
}),
]
}
ok 执行npm run build
,我们可以看到dist目录下生成了static/css/xxx.css文件,bundle.js文件里已没有css,这样我们就做到了css的拆分!
图片发布(css中的img)
同样处理图片也需要相应的loader。
npm i file-loader url-loader -D
接下来我们加入配置:(为了方便观看,只显示当前配置项)
const path = require('path');
// 解决css 分离后图片引入路径不正确问题
if (process.env.type == 'build') { // 判断package.json里面是build还是dev命令
// 开发
var website ={
publicPath:"/"
}
} else {
// 生产
var website ={
publicPath:"/"
}
}
module.exports = {
output: {
filename: 'bundle.[chunkhash:6].js',
path: path.resolve('dist'),
publicPath: website.publicPath // 解决css 分离后图片引入路径不正确问题
},
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000, // limit:是把小于xxx(也就是xxxk)的文件打成Base64的格式,写入JS。否则就写入路径
name: '[name]_[hash:7].[ext]', // 打包图片的名字
outputPath:'static/images/' // 打包后放到images路径下
}
}
]
},
]
},
}
需要注意如果是在css文件里引入的如背景图之类的图片,就需要指定一下相对路径!
-
limit
阈值 单位byte 大于设置值不进行base64压缩,否则下雨此值压缩成base64 -
name
打包图片的名字 -
outputPath
打包图片路径
ok 执行npm run build,我们可以看到dist目录下生成了static/images/xxx.png, 或者直接在css已base64码引入
图片发布(页面中的img)
在实际开发中,我们经常用的<img src="xxxx.png"/>
的方式引入图片,同样也有相应的loader去处理。
npm i html-withimg-loader -D
接下来我们加入配置:(为了方便观看,只显示当前配置项)
module.exports = {
module: {
rules: [
{
test: /\.(htm|html)$/,
use: 'html-withimg-loader'
}
]
}
}
ok,这样我们html和css中的img都可以打包成功了!
自动处理CSS3属性前缀
为了浏览器的兼容性,有时候我们必须加入-webkit,-ms,-o,-moz这些前缀。目的就是让我们写的页面在每个浏览器中都可以顺利运行
PostCSS是一个CSS的处理平台,它可以帮助你的CSS实现更多的功能。
npm i postcss-loader autoprefixer -D
安装后,在项目根目录下创建一个postcss.config.js文件,配置如下:
module.exports = {
plugins: {
// 处理 @import
'postcss-import': {},
// 处理 css 中 url
'postcss-url': {},
// 自动前缀
'autoprefixer': {
"browsers": [
"> 1%",
"last 2 versions"
]
}
}
}
这样我们就实现了css前缀自动补齐了。
babel-loader(解析ES6、ES7)
在实际开发中基本都开始使用ES6的语法了,可以提高前端开发速度,但是有一些低版本的浏览器不支持语法,所以我们需要转成es5。于是就需要Babel。执行下面命令:
npm i babel-core babel-loader babel-preset-env babel-preset-react babel-plugin-transform-runtime babel-preset-stage-0 -D
安装完成后,我们可以新建.babelrc文件来配置一下:
{
"presets": ["react", "env", "stage-0"],
"plugins": ["transform-runtime"],
}
我们再在webpack里配置一下babel-loader既可以做到代码转成ES5了
module.exports = {
module: {
rules: [
{
test:/\.(jsx|js)$/,
include: [
path.resolve(__dirname, 'src'),
// 限定只在 src 目录下的 js/jsx 文件需要经 babel-loader 处理
// 通常我们需要 loader 处理的文件都是存放在 src 目录
],
use:['babel-loader'],
exclude:/node_modules/ // 去除掉node_modules文件夹的js 不然node_modules也都转换了
},
]
}
}
细心的同学可以发现,我加了一个jsx,这样就可以写react代码啦···
resolve解析
在webpack的配置中,resolve我们常用来配置别名和省略后缀名
module.exports = {
resolve: {
// 别名
alias: {
$: './src/jquery.js'
},
// 省略后缀
extensions: ['.js', '.json', '.css']
},
}
优化黑科技
webpack给我们提供了很多优化的配置,接下来我们看看:
拆分JS 抽离公共代码
在webpack4之前,提取公共代码都是通过一个叫CommonsChunkPlugin的插件来完成的,但是webpack4.x以后内置了一个更好的配置项去完成这份工作:
// 提取公共代码
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
vendor: { // 抽离第三方插件
test: /node_modules/, // 指定是node_modules下的第三方包
chunks: 'initial',
name: 'vendor', // 打包后的文件名,任意命名
// 设置优先级,防止和自定义的公共代码提取时被覆盖,不进行打包
priority: 10
},
utils: { // 抽离自己写的公共代码,utils这个名字可以随意起 (css/js公用的都会单独抽离出来生成一个单独的文件)
chunks: 'initial',
name: 'utils', // 任意命名
minSize: 0 // 只要超出0字节就生成一个新包
}
}
}
},
}
备注信息代码已注释的很详细,这里就不多解释了,
-
vendor
开发中我们经常用到vue.min.js、reacr.min.js ....等很多源码库,因为我们每次发布的时候变得只是业务逻辑,源码是不变的,所以我们单独抽离出来,这样也有利于浏览器缓存,提高页面加载速度,增加用户体验。 -
utils
这个配置项指的是平常开发中,我们经常会写一些公共的util,配置此项也可以单独打包出来,但是我不建议这么做,因为本身util一般也不会很大,没有这个必要。
HappyPack
HappyPack 允许 Webpack 使用 Node 多线程进行构建来提升构建的速度。
HappyPack支持对css loader 和 babel loader的加速。对url-loader 和 file-loader 的支持度的问题
首先安装:
npm i -D happypack
接下来我们加入配置:(为了方便观看,只显示当前配置项)
const HappyPack = require('happypack'); // 多线程构建
const happyThreadPool = HappyPack.ThreadPool({ size: 5 }); // 构造出共享进程池,进程池中包含5个子进程
module.exports = {
module: {
rules: [
{
test:/\.(jsx|js)$/,
// 开启多线程进行构建 匹配下面注释的插件
use:['happypack/loader?id=js'],
exclude:/node_modules/
},
]
},
plugins: [
// 多线程构建 匹配上面的loader
new HappyPack({
id: 'js',
loaders: ['babel-loader'],
threadPool: happyThreadPool, // 使用共享进程池中的子进程去处理任务
}),
]
}
其实很简单,我们看奥下面的happypack插件里面的id去匹配上面loader等于js的配置
-
threadPool
开启5个子进程去处理任务
minimizer
在production模式,该配置会默认为我们压缩混淆代码,但这显然满足不了我们对于优化代码的诉求。下面是最优化配置:
optimization: {
minimizer: [
// 自定义js优化配置,将会覆盖默认配置 最大化压缩成js
new UglifyJsPlugin({
exclude: /\.min\.js$/, // 过滤掉以".min.js"结尾的文件,我们认为这个后缀本身就是已经压缩好的代码,没必要进行二次压缩
cache: true,
parallel: true, // 开启并行压缩,充分利用cpu
sourceMap: false,
extractComments: false, // 移除注释
uglifyOptions: {
compress: {
unused: true,
warnings: false,
drop_debugger: true
},
output: {
comments: false
}
}
}),
// 用于优化css文件 最大化压缩css 并且去掉注释掉的css
new OptimizeCssAssetsPlugin({
assetNameRegExp: /\.css$/g,
cssProcessorOptions: {
safe: true,
autoprefixer: { disable: true },
mergeLonghand: false,
discardComments: {
removeAll: true // 移除注释
}
},
canPrint: true
})
],
}
最后
最终代码
const path = require('path');
const webpack = require('webpack');
// 插件都是一个类,所以我们命名的时候尽量用大写开头
const HtmlWebpackPlugin = require('html-webpack-plugin'); //打包html
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 提取出来css
const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); // 压缩打包后的js
const HappyPack = require('happypack'); // 多线程构建
const happyThreadPool = HappyPack.ThreadPool({ size: 5 }); // 构造出共享进程池,进程池中包含5个子进程
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')// 最大化压缩css
console.log('process.env.NODE_ENV------->', process.env.NODE_ENV)
// 解决css 分离后图片引入路径不正确问题
if (process.env.type == 'build') { // 判断package.json里面是build还是dev命令
// 开发
var website ={
publicPath:"/"
}
} else {
// 生产
var website ={
publicPath:"/"
}
}
module.exports = {
// devtool:'eval-source-map',
mode: 'development', // 模式配置
entry: {
main: './src/index.js',
},
output: {
filename: 'bundle.[chunkhash:6].js',
path: path.resolve(__dirname, 'dist'),
publicPath: website.publicPath, // 解决css 分离后图片引入路径不正确问题
},
module: {
rules: [
{
test: /\.css/,
exclude: /node_modules/,
use: ['style-loader', MiniCssExtractPlugin.loader, 'css-loader'],
},
{
test: /\.less$/,
exclude: /node_modules/,
use: ['style-loader', MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'],
},
{
test: /\.(png|jpe?g|gif|svg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000,
name: '[name]_[hash:7].[ext]',
outputPath:'static/images/'
}
}
]
},
{
test: /\.(htm|html)$/,
use: 'html-withimg-loader'
},
// babel 解析es7 es6 jsx
{
test:/\.(jsx|js)$/,
include: [
path.resolve(__dirname, 'src'),
],
use:['babel-loader'],
/*
如果开启多线程进行构建
use:['happypack/loader?id=js'],
loader这样写 匹配下面注释的插件
*/
exclude:/node_modules/
},
]
},
plugins: [
// 打包html
new HtmlWebpackPlugin({
template: './src/index.html',
hash: true,
minify: {
minifyCSS: true,
minifyJS: true,
removeAttributeQuotes: true
},
}),
new MiniCssExtractPlugin({
filename: "static/css/[name].[chunkhash:8].css",
chunkFilename: "[id].css"
}),
new UglifyJsPlugin({
parallel: true,
}),
new webpack.DefinePlugin({
NODE_ENV: JSON.stringify('DEV')
}),
// 多线程构建 匹配上面的loader
// new HappyPack({
// id: 'js',
// //threads: 4,
// loaders: ['babel-loader'],
// threadPool: happyThreadPool, // 使用共享进程池中的子进程去处理任务
// }),
],
// 提取公共代码
optimization: {
minimizer: [
// 自定义js优化配置,将会覆盖默认配置 最大化压缩成js
new UglifyJsPlugin({
exclude: /\.min\.js$/, // 过滤掉以".min.js"结尾的文件,我们认为这个后缀本身就是已经压缩好的代码,没必要进行二次压缩
cache: true,
parallel: true, // 开启并行压缩,充分利用cpu
sourceMap: false,
extractComments: false, // 移除注释
uglifyOptions: {
compress: {
unused: true,
warnings: false,
drop_debugger: true
},
output: {
comments: false
}
}
}),
// 用于优化css文件 最大化压缩成css 并且去掉注释掉的css
new OptimizeCssAssetsPlugin({
assetNameRegExp: /\.css$/g,
cssProcessorOptions: {
safe: true,
autoprefixer: { disable: true },
mergeLonghand: false,
discardComments: {
removeAll: true // 移除注释
}
},
canPrint: true
})
],
splitChunks: {
cacheGroups: {
vendor: { // 抽离第三方插件
test: /node_modules/, // 指定是node_modules下的第三方包
chunks: 'initial',
name: 'vendor', // 打包后的文件名,任意命名
// 设置优先级,防止和自定义的公共代码提取时被覆盖,不进行打包
priority: 10
},
// utils: { // 抽离自己写的公共代码,utils这个名字可以随意起 (css/js公用的都会单独抽离出来生成一个单独的文件)
// chunks: 'initial',
// name: 'utils', // 任意命名
// minSize: 0 // 只要超出0字节就生成一个新包
// }
}
}
},
devServer: {
historyApiFallback: true,
inline: true
},
// externals: {
// jquery: "jQuery",
// },
resolve: {
// alias 别名配置,它能够将导入语句里的关键字替换成你需要的路径
alias: {
// 比如我们就可以直接写 import Nav from '@/Nav'
'@': './app/component'
},
// 省略后缀
extensions: ['.js', '.jsx', '.less', '.json', '.css'],
},
performance: {
hints: false // 选项可以控制 webpack 如何通知「资源(asset)和入口起点超过指定文件限制」
}
}
同学你都读到这里了,还不点赞加关注吗?
webpack其实还有很多优化项,很多plugins And loader等着我们去挖掘.
小编也是萌新一枚,哪里写的不好,提出意见!我也会不断更新文章的...请大家持续关注!
辛苦大家了.......下班回家了............