小知识,大挑战!本文正在参与「程序员必备小知识」创作活动
本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。
引言
webpack作为前端最常用的项目构建工具,但却常常因为其繁琐的配置让人望而却步,这篇文章将从最基础的部分带领大家一步一步配置webpack
Webpack是什么
Webpack是目前最常用的前端资源构建工具,在Webpack看来,前端所有资源文件(.css/js/jsx/img/png/sass...)都会作为模块处理。Webpack根据你的项目结构,将ES6或者其他浏览器不能直接使用的拓展语言,例如typescript、sass、vue等,通过Loaders处理打包成浏览器能读懂的语言,以便在浏览器上能够正确运行
安装与使用
- 新建文件夹,使用
npm init -y初始化npm
mkdir webpack
cd webpack
npm init -y
- 安装
webpack、webpack-cli
npm install webpack webpack-cli -D
- 新建
src文件夹,新建index.html和index.js,修改index.js
// index.js
console.log('hello webpack!')
- 在控制台输入
npx webpack
npx webpack
可以看到在项目根目录下生成了dist文件夹,并且里面有main.js,说明webpack打包成功!
Webpack基本配置
在项目根目录下新建webpack配置文件webpack.config.js
入口 entry
entry规定了webpack构建的入口文件,它决定了webpack从哪个模块开始生成依赖图
// webpack.config.js
module.exports = {
// 入口
entry: {
main: './src/index.js'
}
}
多个入口配置:
// webpack.config.js
module.exports = {
// 入口
entry: {
one: './src/one.js',
two: './src/two.js'
}
}
// 或
module.exports = {
// 入口
entry: {
main: [
'./src/one.js',
'./src/two.js'
]
}
}
出口 output
output字段规定webpack应该在哪里输出它构建的文件path,以及如何命名文件filename
// webpack.config.js
const path = require('path')
module.exports = {
// 入口
entry: {
main: './src/index.js'
},
// 出口
output: {
path: path.resolve(__dirname, 'dist'), // ./dist
filename: 'main.js'
}
}
模式 mode
提供 mode 配置选项,告知 webpack 使用相应模式的内置优化。
- development
会将
process.env.NODE_ENV的值设为development。启用 NamedChunksPlugin 和 NamedModulesPlugin。
module.exports = {
// ...
mode: 'development'
}
- production
会将
process.env.NODE_ENV的值设为production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin...
module.exports = {
// ...
mode: 'production'
}
也可以在运行打包命令时规定打包模式
npx webpack --mode developmenet
Loader
Loaders是webpack的核心概念之一,由于webpack默认只能处理js文件,那么在遇到其他文件类型css/jpg/png/ts...时,需要通过不同的Loader处理不同类型的文件,例如将ES6转化为浏览器能够读懂的ES5,将TypeScript转化成JavaScript,将sass转化为css...它有点类似于工厂流水线上的机器,将生产原料一步步处理打包,最终输出能够使用的产品
Loaders的配置只有简单的几项(*为必填项):
- test:匹配对应文件格式的正则表达式(*)
- use:使用的loader(*)
- include/exclude:需要处理的文件/不需要处理的文件
使用file-loader打包jpg/png/gif图片
const path = require('path')
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
},
module: {
rules: [
{
test: /.(jpg|png|gif)$/,
use: {
loader: 'file-loader',
options: { // 为loader提供额外的配置(可选)
// 占位符[]
name: '[name].[ext]', // 使用原来的文件名和后缀
// 也可以为 [name]_[hash].[ext] 文件名_哈希值
outputPath: 'images/' // 输出路径
}
}
}
]
}
}
运行npx webpack可以看到dist目录下出现了图片
使用url-loader打包图片
url-loader会将图片格式转化为Base64格式
const path = require('path')
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
},
module: {
rules: [
{
test: /.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]', // 使用原来的文件名和后缀
outputPath: 'images/',
limit: 2048 // 限制文件不得超过2048字节,也就是2KB
// 如果超过,就使用file-loader
}
}
}
]
}
}
假如我们想把图片的样式修改宽高为300px,通常做法是新建index.css文件,写入样式后在index.js文件中引入
.avatar {
width: 300px;
height: 300px;
}
在index.js文件中引入
import image from './psc.jpg'
import './index.css'
const img = new Image()
img.src = image
img.className += 'avatar'
const app = document.getElementById('app')
app.appendChild(img)
此时我们运行npx webpack可以发现控制台报错:
这是因为webpack无法解析
.css文件,这时候我们需要相应的Loader来对此类型文件进行处理CSS文件
使用css-loader和style-loader打包样式
安装css-loader和style-loader
npm install css-loader style-loader -D
// webpack.config.js
{
test: /.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'sass-loader'
]
}
注意:plugin数组的执行顺序是 从下往上 / 从右往左 依次处理
使用sass-loader处理scss文件
npm install sass sass-loader -D
body {
.avatar {
width: 300px;
height: 300px;
}
}
执行顺序:sass-loader先将sass文件转换为css文件,再交给css-loader和style-loader
module.exports = {
// ...
{
test: /.scss$/,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
}
}
为css3属性加上浏览器前缀
对于一些CSS3属性,我们希望给它加上对应浏览器的前缀(例如-webkit-),来保证它能在各个浏览器中正确运行。postcss-loader能够为我们自动加上浏览器前缀
npm install postcss postcss-loader autoprefixer -D
// webpack.config.js
// ...
{
test: /.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'sass-loader'
]
}
新建postcss.config.js
// postcss.config.js
module.exports = {
plugins: [
require('autoprefixer') // 引入autoprefixer插件
]
}
这个插件内部会按照package.json中的browserslist属性来添加对应前缀,因此我们需要对package.json加入相应配置
// package.json
"browserslist": [
"> 1%", // 需要兼容市场份额百分之1的浏览器
"last 2 versions" // 需要兼容上两个版本
]
Babel处理ES6
安装loader
npm install -D babel-loader @babel/core @babel/preset-env
注意:这里有三个模块需要安装,不要漏了=.=
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'images/'
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new CleanWebpackPlugin()
]
}
打包输出后的main.js/bundle.js
可以看到,这里有一个问题:Babel只能转换ES6语法,而不能转换新的API,比如Generator、Set、Maps、Proxy、Symbol、Promise、Async等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转化。对于这些,我们可以用babel-polyfill来处理
Babel处理ES6新api
安装(注意要用--save,因为线上环境也需要用到):
npm install @babel/polyfill --save
在index.js文件中引入babel-polyfill
import '@babel-polyfill'
打包输出后的main.js
可以看到main.js中多出了很多代码,打包完成的js文件大小为405KB,而我们在使用babel-polyfill打包前的代码大小只有264个字节(Bytes),为什么两次打包的文件大小相差这么大呢?
原因是
webpack把babel-polyfill整体全部都打包进去了。而babel-polyfill肯定也实现了所有ES6新API的垫片,文件一定不会小。
我们可以在babel-loader中加入useBuiltIns配置,告诉babel-polyfill只需要将使用到的es6代码进行打包,以避免不必要的打包,减少打包输出体积。
// webpack.config.js
modules.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage'
}
]
]
}
}
}
]
}
}
输入命令后可以看到打包输出的代码大小只有192KB大小,明显小了不少
Plugin
Plugin是webpack另一个核心的概念,它可以在webpack运行到某个阶段的时候拓展webpack的功能。这里推荐两个常用的plugin
- html-webpack-plugin html-webpack-plugin在打包完毕之后自动帮我们生成html文件,并将打包后的结果注入到html中。
安装
npm install html-webpack-plugin -D
使用
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
},
module: {
rules: [
{
test: /.(jpg|png|gif)$/,
use: {
loader: 'file-loader',
options: { // 为loader提供额外的配置(可选)
// 占位符[]
name: '[name].[ext]', // 使用原来的文件名和后缀
// 也可以为 [name]_[hash].[ext] 文件名_哈希值
outputPath: 'images/' // 输出路径
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin()
]
}
接收模板配置
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
},
module: {
rules: [
{
test: /.(jpg|png|gif)$/,
use: ['file-loader']
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
}
这里我们让打包后引入index.js文件的过程自动在index.html中注入
- clean-webpack-plugin
每次在
npm run build之前我们都需要手动删除之前的dist文件夹,clean-webpack-plugin可以帮助我们在输出打包完的文件前,自动先删除之前生成的dist文件夹
安装
npm install clean-webpack-plugin -D
使用
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new CleanWebpackPlugin()
]
}
SourceMap
浏览器在实际运行时执行的是经webpack处理后的文件main.js或bundle.js,那么问题来了,如果代码中出现错误,浏览器在控制台中报错指向的是打包后的代码main.js,也就是说如果我们在源代码index.js的第5行出了错,在打包后的代码中不知道指向的是第几行,由此SourceMap应运而生,SourceMap是一项将编译、打包、压缩后的代码映射回源代码的技术,它可以将打包后的代码准确指向到源代码对应的位置上
配置SourceMap
// 开发环境
module.exports = {
mode: 'development',
devtool: 'cheap-module-source-map',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new CleanWebpackPlugin()
]
}
WebpackDevServer
在每次修改代码后,我们都需要重新运行npx webpack才能看到修改后的效果,那么在webpack中有没有什么办法能够为我们在修改代码后保存自动进行打包呢?webpack-dev-server可以在本地启动一个服务器,并让打包输出的文件运行在服务器上,这样一来,我们在修改了代码保存后,webpack-dev-server会自动为我们进行打包
安装
npm install webpack-dev-server -D
在package.json中加入以下命令
// package.json
"scripts": {
"watch": "webpack --watch",
"start": "webpack serve"
}
运行npm run start启动webpack服务器,并运行在localhost:8080
open:自动打开浏览器
// webpack.config.js
devServer: {
contentBase: './dist', // webpack-dev-server3
open: true
}
运行npm run start后可以看到webpack自动为我们打开浏览器并访问localhost:8080
使用WebpackDevServer实现请求转发
在开发过程中经常需要用到http请求,proxy可以在你请求接口时,将请求转发到别的url上
// webpack.config.js
module.exports = {
// ...
devServer: {
contentBase: './dist',
proxy: {
'/api': {
target: 'http://localhost:3000'
}
}
}
}
axios.get('/api/users').then(res => {
// ...
})
这个时候,如果发送请求到 /api/users,会被代理到请求 http://localhost:3000/api/users。
HMR 模块热替换
不需要刷新页面,保存即更新。也就是说已经渲染到页面上的内容不会消失,而是自动根据修改的代码直接应用到页面上
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
devserver: {
contentBase: './dist',
open: true,
hot: true // 开启HMR
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'images/'
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new CleanWebpackPlugin()
]
}
注意:在Webpack4中HMR是默认开启的
总结
本文详细介绍了以下webpack的一些基础配置,希望能够帮助前端小白入门webpack,webpack配置的过程看起来非常复杂,从webpack官网也能看到配置项也非常的多,但是不要被它表面的繁琐吓住了,主要理清楚webpack的逻辑,一步一步配置好对应项,其实webpack也就(zhen)这(de)样(nan)
最后,如果本文对你有帮助的话,留下一个赞吧~