此篇文章将简单介绍webpack的基本概念和使用方法,并通过实例演示如何使用webpack来构建一个不依赖前端框架的项目,同时也会介绍一些常用的webpack插件和优化技巧,希望本篇文章能够帮助大家更好地使用webpack进行项目开发。
常见打包工具有Grunt、Gulp、Parcel、Webpack、Rollup、Vite、ESBuild 等等,这篇文章只专注于webpack就不去对比其他打包工具了。webpack优点在于有着完善的基础建设,而且得益于webpack5的升级,对比以往版本也有了相当大的提升(极致编译速度,一文搞定webpack5升级),虽然它的保留率在逐年下降,但大概率还是目前使用率最多的构建工具(查看StateOfJS的调查结果)。
sass
,TypeScript
等代码虽然极大的提高了开发效率,但是本身并不被浏览器所识别,需要我们对其进行编译和打包,变成浏览器识别的代码webpack是用Node封装的,可以使用Node的语法,Node.js 的模块规范就是 CommonJS
模块规范,ES6模块化(ES Modules / ESM)是浏览器端和服务器端通用的规范,关于规范的区别这里也不展开了。
下面正式进入webpack5的实战环节,我使用Node的版本为14.21.3,当然最好还是更新到比较新的Node版本进行webpack5的开发,不过Webpack 5 运行于 Node.js v10.13.0+ 的版本,我这里使用较低版本也是为了兼顾大多数的开发环境。首先创建基础的网页,html文件中放入图片及引用css、script等,目录结构如下:
├── webpack5 (项目根目录)
├── image (项目图片资源目录)
│ └── 略
├── favicon.ico(网页图标)
├── index.css
├── index.html
└── index.js
npm init -y
npm i webpack webpack-cli -D
在package.json中的scripts中创建build
命令,webpack默认会找配置文件“webpack.config.js”
"scripts": {
"build": "webpack"
},
const path = require('path'); //核心模块
module.exports = {
entry: 'index.js', //配置入口文件
output: {
filename: 'bundle.js', //打包出来的文件名称
path: path.resolve(__dirname, './dist'), //打包后的路径,必须是绝对路径
//path.resolve()拼接路径为绝对路径
//__dirname指当前文件根目录,dist是打包出来的目录名称
},
mode: 'production', //production生产模式(代码压缩),development开发模式(不压缩代码)
module: {}, //配置loader
plugins: [] //配置plugins
};
运行了webpack打包命令后多了dist目录,类似下面这样子就证明成功使用webpack5打包了。
查看bundle.js文件会发现只有对index.js代码压缩后的内容,这是正常的,因为我们只是指定了“index.js”作为入口起点(entry point) ,让 webpack 使用其作为一个“模块”构建项目,其他资源文件(如html、css)要引入到 js 里面才能使用。
另外webpack本身就能打包 js 代码,但是其他类型的如css,图片等格式的文件没法打包,就需要在引入资源后利用第三方的模块进行打包,这就是loader(加载器) 的作用了,另外配置了多个loader,其按照从右到左(或从下到上)地取值(evaluate)/执行(execute)。
npm i babel-loader @babel/core @babel/preset-env core-js @babel/plugin-transform-runtime @babel/preset-typescript -D
module: {
rules: [
{
test: /\.js|ts$/,
use: {
loader: 'babel-loader',
options: {
exclude: /node_modules/, //除了这个文件夹之外的文件
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage', //按需引入(根据配置的目标环境找出需要的polyfill进行部分引入)
corejs: 3,
// 设置兼容目标浏览器版本,这里可以不写,将使用 browserslist 配置文件
// targets: '> 1%, not dead',
},
],
'@babel/preset-typescript',
], //预设
// 插件比预设先执行
plugins: ['@babel/plugin-transform-runtime'],
cacheDirectory: true, //开启缓存,第二次编译时,没改的部分使用缓存
},
},
},
]
}
可以把babel-loader的配置单独抽离出来,配置成一个全局配置文件babel.config.js,也可以使用**.babelrc**文件进行颗粒度更细的配置,这两者可以一起使用,也可以独立使用,当同级存在时后者优先级更高,详细可看babeljs官网,或者理解 babel.config.js 和 babelrc。
在package.json中添加browserslist:
"browserslist": [
">1%",
"last 10 versions",
"not dead"
]
module: {
rules: [
{
test: /\.(png|jpe?g|gif|webp|svga)(\?.*)?$/,
type: 'asset',
generator: {
filename: 'imgs/[name].[contenthash:4][ext]',
},
parser: {
// 生成Data URI 的条件,视为 inline 模块类型,否则会被视为 resource 模块类型
dataUrlCondition: {
// 当资源模块不超过 4kb 时,生成 DataURI,超过 4kb 时,单独打包成文件
maxSize: 4 * 1024, // 4b
},
},
},
{
test: /\.(eot|ttf|otf|woff2?)$/,
type: 'asset/resource',
generator: {
filename: 'font/[name].[contenthash:4][ext]',
},
},
]
}
npm i style-loader mini-css-extract-plugin
npm i sass sass-loader -D
npm i less less-loader -D
npm i postcss-loader autoprefixer -D
另外还需要有一份要兼容浏览器的清单,让postcss-loader知道要加哪些浏览器的前缀,browserslist配置文件其实就是这份清单。
npm i css-minimizer-webpack-plugin -D
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const devMode = process.env.NODE_ENV !== 'production';
module.exports = {
// ...
module: {
rules: [
{
test: /\.css$/,
//use里面执⾏顺序是从右往左的顺序,先模块化再引入
use: [
devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
url: true, //默认为true,可以处理css中的url图片路径
esModule: false,
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: ['autoprefixer'],
},
},
},
],
},
{
test: /\.scss$/,
use: [
devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: ['autoprefixer'],
},
},
},
'sass-loader',
],
},
{
test: /\.(png|jpe?g|gif|webp|svga)(\?.*)?$/,
type: 'asset',
generator: {
filename: 'imgs/[name].[contenthash:4][ext]',
},
parser: {
// 生成Data URI 的条件
dataUrlCondition: {
// 当资源模块不超过 4kb 时,生成 DataURI,超过 4kb 时,单独打包成文件
maxSize: 4 * 1024, // 4b
},
},
},
{
test: /\.(eot|ttf|otf|woff2?)$/,
type: 'asset/resource',
generator: {
filename: 'font/[name].[contenthash:4][ext]',
},
},
// ...
],
},
plugins: [
new MiniCssExtractPlugin({
// 长期缓存:使用 filename: "[contenthash].css" 启动长期缓存
filename: devMode ? 'css/[name].css' : 'css/[name].[contenthash].css',
}),
],
};
这里其实会有个问题,当我们指定mode的模式为“production”后,在index.js文件中打印process.env.NODE_ENV的值确实是“production”,然而在webpack.config.js文件中打印process.env.NODE_ENV的值会发现值是’undefined’,这里先卖个关子,解决办法留到后面的环境变量章节讲述。
webpack需要把最终构建好的静态资源都引入到一个html文件中,这样才能在浏览器中运行
npm i html-webpack-plugin [email protected] -D
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// ...
plugins: [
new HtmlWebpackPlugin({
template: template: path.resolve(__dirname, './index.html'), //模板
filename: 'index.html', //打包的 HTML 文件名字
favicon: path.resolve(__dirname, './favicon.ico'), //网页图标
}),
]
}
module: {
rules: [
//...
{
test: /\.html$/i,
use: [
{
loader: 'html-loader',
options: {
esModule: false,
},
},
],
},
//...
]
}
因为webpack配置的打包入口只有index.js一个,记得在index.js文件中引入需要的资源,如
import './index.scss';
import './index.ts';
再次运行webpack打包,就可以打包出来如下图所示的目录文件了,本地运行这个index.html文件,能正常访问图片、应用样式以及控制台成功执行输出等就证明成功打包了。
然而其实这里有点小问题,一是资源报错找不到,二是重复加载资源。
针对其一,原因是原来的html页面引入了这些资源,现在将其去掉即可。
针对其二,由于在HtmlWebpackPlugin插件中配置了favicon选项,虽然将favicon.ico文件从根目录拷贝到打包目录下了,但是这个插件并没有智能地将原本html里引用的favicon.ico去掉,导致重复加载了,有两个办法:一是直接删掉html里引入的favicon.ico,二是使用copy-webpack-plugin
这个插件,webpack只会将里面的资源复制到打包目录下,不会对其进行压缩编译等处理。
npm i copy-webpack-plugin -D
const CopyPlugin = require('copy-webpack-plugin');
module.exports = {
plugins: [
// ...
// public目录被webpack打包,一开始是什么样子,打包后还是什么样子
new CopyPlugin({
patterns: [
{
from: 'public', //拷贝的目录
},
],
}),
],
};
将favicon.ico从根目录移动到新创建的public文件夹中,再次运行打包命令即可。
写到这里这篇文章篇幅实在是太长了,所以还有几章关于webpack的环境变量、插件、优化等内容我还是另开一篇文章讲述,后续文章请点击查看。
欢迎各位看官【点赞】、【收藏】,多多支持!也欢迎您【评论】留下宝贵意见,共同探讨一起学习~。