webpack 打包前端静态项目
前言
最近新写了一个 H5 项目,不需要后端交互,使用了原生js
,然后再次体会到webpack的强大。
最开始项目只有一个index.html
和 几个js
文件,js
文件直接在index.html
通过<script>
标签进行依次引入,感觉没有太大问题。之后这个项目在服务器使用nginx
做个简单托管也就部署好了,但是部署的时候我需要对这些js
文件进行压缩来缩小资源体积,同时转换ES6、ES7语法到浏览器普遍支持的 ES5,我当时就想反正就几个js
文件嘛,按照在index.html
的引入顺序全部复制到一个js
文件里,再把这个文件复制到一个js
在线压缩工具进行压缩就OK啦。
<script src="preset.js"></script>
<script src="utils.js"></script>
<script src="features.js"></script>
<script src="main.js"></script>
<!-- 最初傻瓜想法:按引入顺序把其内容复制到一个新js文件里,再压缩好了复制回这里只留一个 script 标签 -->
之后为了进一步减小项目体积,我又把html
、css
也复制到对应的在线压缩工具进行压缩,最后又为了减少网络请求,又将css
内容直接放到head
的style
下,而不使用link
标签。
后来js
逻辑变多了,得使用模块语法了,Oops!这可怎么办呢,然后我就想到了webpack~。我最后只需要yarn build
一下,一切问题都搞定了。
配置 webpack
可参考webpack 中文文档。
使用
yarn
添加一下webpack
,顺便生成package.json
。yarn add -D webpack webpack-cli
写一个简单的配置文件
webpack.config.js
设置一下入口文件和输出文件。const path = require('path') module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), }, }
项目
src/index.js
这个入口文件就使用了模块语法,举个例:import AudioController from './AudioController' import { refreshAll } from './Timer' const canvas = document.getElementById('content') const ctx = canvas.getContext('2d') canvas.onclick = event => { const relativeY = event.pageY - canvas.offsetTop if (relativeY > 200 && relativeY < 400) { refreshAll() ctx.fillText('Welcome to Anand\'s Blog', 120, 120) } }
为 webpack 打包写一个
script
,在package.json
的scripts
添加build
操作,这样之后打包只需要运行yarn build
就好啦。{ "private": true, "scripts": { "build": "webpack" }, "devDependencies": { "webpack": "^5.13.0", "webpack-cli": "^4.3.1" } }
使用
yarn build
命令进行打包,在dist
目录会生成bundle.js
文件,然后去除index.html
里之前引入js
文件的script
标签,现在不需要再像之前一样还要考虑引入顺序,只需要引入这个bundle.js
就好啦。<script src="dist/bundle.js"></script>
Nice! 这样简单的配置,就解决了之前复制几个js
文件到一起然后再压缩、转语法的操作了。
丰富 webpack
虽然现在已经能满足js
文件打包、压缩等需求了,但是js
文件里使用图片、音频等文件等路径怎么办呢?
图片、音频资源
如何项目只有图片资源,只需要简单添加module
规则配置。
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource'
}
]
}
}
但是我的这个项目还会用到音频资源,所以就需要借助file-loader
了,先安装一下。
yarn add -D file-loader
顺便配置一下资源解析,每次引入图片、音频这些资源的路径都需要./
、../../
慢慢去找,这也太麻烦了吧。我们配置一下resolve
。
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
// 只加了 mp3 格式,其他的也可以,继续加就好啦
test: /\.(png|svg|jpg|jpeg|gif|mp3)$/i,
loader: 'file-loader',
options: {
// [path]、[hash] 这些都是占位符,其他的可以看 file-loader 文档
name: '[path][hash:18].[ext]'
}
}
]
},
resolve: {
alias: {
// assets/ 是我项目资源的位置
// 之后使用 import titleImage from 'Assets/images/title.png' 就可以啦,require 也一样
Assets: path.resolve(__dirname, 'assets/')
}
}
}
但是项目中new Audio('Assets/audios/demo.mp3')
会存在问题,在chrome
的开发工具看请求是[object%20Module]
,挺奇怪。
之后发现这些资源的解析不需要使用到ES modules
语法,我们把它关掉:
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.(png|svg|jpg|jpeg|gif|mp3)$/i,
loader: 'file-loader',
options: {
name: '[path][hash:18].[ext]',
// 还有一个解决方案是使用 require('Assets/audios/demo.mp3').default 而不用关闭这个配置
esModule: false
}
}
]
},
resolve: {
alias: {
Assets: path.resolve(__dirname, 'assets/')
}
}
}
配置需要的插件
现在还存在其他需求:html
文件的压缩,js
文件自动引入到html
、css
文件的压缩等。
html-webpack-plugin
如果修改了打包输出文件名,或者文件名使用了hash
,这样打包都都需要手动去修改index.html
里js
文件的引入,太麻烦了,我们使用html-webpack-plugin
插件来解决,同时解决了压缩需求。
安装一下
yarn add -D html-webpack-plugin clean-webpack-plugin
clean-webpack-plugin
插件可以每次打包前自动删除打包生成的dist
文件夹,可以不用。配置一下
const { CleanWebpackPlugin } = require('clean-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { // 为了避免代码块太长,这里省略啦其他配置项 plugins: [ new CleanWebpackPlugin(), // 使用项目的 index.html 作为模板,打包后会在 dist 生成一个新的 index.html(插入了 bundle.js 的 <script> 引入标签) new HtmlWebpackPlugin({ template: 'index.html' }) ] }
mini-css-extract-plugin
使用mini-css-extract-plugin
插件整合项目使用的多个css
文件到一个文件。简单使用可以看它的文档,这里我的需求是项目不存在import 'Assets/styles/main.css'
这样的资源使用,只在index.html
中通过link
加载。所以我们需要在 webpack 的entry
入口配置中添加css
文件,然后再利用这个插件,并且html-webpack-plugin
插件也自动把输出文件引入到了index.html
,完美。
安装一下,同时需要用到
css-loader
。yarn add -D mini-css-extract-plugin css-loader
webpack 配置中添加这个插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') const MiniCssExtractPlugin = require('mini-css-extract-plugin') module.exports = { // 为了避免代码块太长,这里省略啦其他配置项 entry: [ './src/index.js', './assets/styles/main.css' ], plugins: [ new CleanWebpackPlugin(), new MiniCssExtractPlugin(), new HtmlWebpackPlugin({ template: 'index.html' }) ], module: { rules: [ // ... 其他已省略 { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] } ] } }
css-minimizer-webpack-plugin
你或许发现了mini-css-extract-plugin
插件并没有压缩输出的css
文件。之前这个需求我们一般用的optimize-css-assets-webpack-plugin
,但是 webpack 5 及更高版本建议使用了css-minimizer-webpack-plugin
。
安装一下
yarn add -D css-minimizer-webpack-plugin
配置一下
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') module.exports = { // 为了避免代码块太长,这里省略啦其他配置项 optimization: { minimize: true, minimizer: [ new CssMinimizerPlugin(), ] }, }
但是使用它之后出现了一下新问题,那就是之前打包的生成的bundle.js
文件不是之前压缩的样子了,这里设置了minimizer
后需要加上 webpack 5 本身带有的terser-webpack-plugin
优化。
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const TerserWebpackPlugin = require("terser-webpack-plugin")
module.exports = {
// 为了避免代码块太长,这里省略啦其他配置项
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin(),
new TerserWebpackPlugin(),
]
},
}
另外,bundle.js
文件中其实会存在一些项目依赖打包后的license
许可注释,但是我们并不需要它存在,可以这样简单的配置一下terser-webpack-plugin
插件。
new TerserWebpackPlugin({
terserOptions: {
format: {
comments: false
}
},
extractComments: false
})
html-inline-css-webpack-plugin
因为我们需要打包压缩生成好的css
文件直接插入到index.html
文件的head
的style
,而不是使用link
方式,所以我们还可以再使用一个插件html-inline-css-webpack-plugin
。
安装一下
yarn add -D html-inline-css-webpack-plugin
配置一下
const HTMLInlineCSSWebpackPlugin = require('html-inline-css-webpack-plugin').default module.exports = { // 为了避免代码块太长,这里省略啦其他配置项 plugins: [ // ...省略了其他项 new HTMLInlineCSSWebpackPlugin() ], }
完成需求的 webpack 配置
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const HTMLInlineCSSWebpackPlugin = require('html-inline-css-webpack-plugin').default
const TerserWebpackPlugin = require("terser-webpack-plugin")
module.exports = {
mode: 'production',
entry: [
'./src/index.js',
'./assets/styles/main.css'
],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: ''
},
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin(),
new HtmlWebpackPlugin({
template: 'index.html'
}),
new HTMLInlineCSSWebpackPlugin()
],
module: {
rules: [
{
test: /\.(png|svg|jpg|jpeg|gif|mp3)$/i,
loader: 'file-loader',
options: {
name: '[path][hash:18].[ext]',
esModule: false
}
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
}
]
},
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin(),
new TerserWebpackPlugin()
]
},
resolve: {
alias: {
Assets: path.resolve(__dirname, 'assets/')
}
}
}
最后
🦢 Oops!太舒服了,现在只需要yarn build
一下,部署需要的资源就在dist
文件里满意的放着了。
版权声明:
Anand's Blog文章皆为站长Anand Zhang原创内容,转载请注明出处。
包括商业转载在内,注明下方要求的文章出处信息即可,无需联系站长授权。
请尊重他人劳动成果,用爱发电十分不易,谢谢!
请注明出处:
本文出自:Anand's Blog