开发相关版本:
-
webpack: 5.51.1版本
-
webpack-cli: 4.8.0版本
-
node: 14.10.0版本
1. 初始化项目
npm init -y
注: -y的含义: yes, 在init的时候省去敲回车的步骤,生成的默认的package.json。
接着安装webpack、webpack-cli
npm i -D webpack webpack-cli
注: npm i -D是npm install --save-dev的缩写, npm i -S是npm install --save的缩写
新建一个文件夹src,还是使用之前两个js:
greeting.js
export function greeting (name) {
return "hello " + name;
}
index.js
import { greeting } from './greeting.js';
document.write(greeting('world!'))
在package.json里配置打包命令:
"scripts": {
"build": "webpack ./src/index.js"
},
执行:
npm run build
如果生成了一个dist文件夹,并且内部含有main.js说明已经打包成功了
2. 新增webpack.config.js
新建config文件夹,接着新建一个webpack.config.js
const path = require("path");
module.exports = {
mode: "development", // 开发模式
entry: "./src/index.js", // 入口文件
output: {
filename: "main.js", // 打包后的文件名称
path: path.resolve(__dirname, "../dist") // 打包后的目录
}
};
更改我们的打包命令
"scripts": {
"build": "webpack --config ./config/webpack.config.js"
},
执行npm run build进行打包
3.配置html模板
3.1 单入口配置
安装html-webpack-plugin
npm i -D html-webpack-plugin
利用html-webpack-plugin去将webpack打包出来的js文件引入到定义好的html模板中, 在根目录新建public,里面新建一个index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>HtmlWebpackPlugin</title>
</head>
<body>
<noscript>
<strong>We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
</body>
</html>
修改webpack.config.js
引入html-webpack-plugin,加上plugins
const HtmlWebpackPlugin = require("html-webpack-plugin");
//...
module.exports = {
// ....
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html")
})
]
}
执行npm run build就可以看到main.js已经引入到html页面中:
如果我们需要频繁的修改js,但要实时查看效果的话则需要引入webpack-dev-server来解决这个问题。
webpack-dev-server
为你提供了一个基本的 web server,并且具有 live reloading(实时重新加载) 功能
npm i -D webpack-dev-server
修改配置文件,告知 dev server,从什么位置查找文件:
webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
mode: "development", // 开发模式
entry: path.resolve(__dirname,'../src/index.js'), // 入口文件
// web server
devServer: {
static: {
directory: path.resolve(__dirname, '../dist'), // 打包后的文件路径
},
open: true, //自动打开浏览器
compress: true, //启动gzip压缩
port: 9000 // 端口号
},
output: {
filename: "main.js", // 打包后的文件名称
path: path.resolve(__dirname, "../dist"), // 打包后的目录
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html")
})
]
};
接着修改package.json里配置命令:
"scripts": {
"dev": "webpack-dev-server --config ./config/webpack.config.js",
"build": "webpack --config ./config/webpack.config.js"
},
为了在浏览器里看效果,我们可以修改src/index.js
import { greeting } from "./greeting.js";
document.getElementById("app").textContent = greeting("world!");
执行npm run dev就可以启动浏览器看实际效果了:
3.2 多入口配置
还是利用html-webpack-plugin来解决, 只是生成多个html-webpack-plugin实例来解决这个问题
在src文件夹下新建search.js
search.js
document.getElementById("app").textContent = "我是search页面";
修改webpack.config.js里面的plugins
// ...
module.exports = {
// ...
plugins: [
new HtmlWebpackPlugin({
template:path.resolve(__dirname,'../public/index.html'),
filename:'index.html',
chunks:['main']
}),
new HtmlWebpackPlugin({
template:path.resolve(__dirname,'../public/index.html'),
filename:'search.html',
chunks:['search'] // 与入口文件对应的模块名
})
]
}
先执行npm run build看下打包后生成的文件:
执行npm run dev看下实际效果:
两个页面都正常
3.3 利用glob对页面需要打包文件的路径进行处理
glob 在webpack中对文件的路径处理非常之方便,比如当搭建多页面应用时就可以使用glob对页面需要打包文件的路径进行很好的处理
安装glob
npm i -D glob
我们先调整下src中目录结构
然后在根目录新建一个getMultiPageConfig.js
getMultiPageConfig.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const glob = require("glob");
exports.getMultiPageConfig = function () {
const entries = {}, htmlPlugins = [];
// glob同步方法获取
const entryFiles = glob.sync(path.resolve(__dirname, "./src/js/*/index.js"));
// ['/xxx/demo05-glob-multpage/src/js/index/index.js', '/xxx/demo05-glob-multpage/src/js/search/index.js']
entryFiles.forEach((ele) => {
let match = ele.match(/src\/js\/(.*)\/index\.js/);
let pageName = match && match[1];
if (pageName) {
entries[pageName] = ele;
htmlPlugins.push(
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "./public/index.html"),
filename: `${pageName}.html`,
chunks: [pageName],
})
);
}
});
return {
entries,
htmlPlugins,
};
};
接着修改webpack.config.js
const path = require("path");
const { getMultiPageConfig } = require("../getMultiPageConfig");
const { entries, htmlPlugins } = getMultiPageConfig();
module.exports = {
mode: "development", // 开发模式
// 入口文件
entry: entries,
// web server
devServer: {
static: {
directory: path.resolve(__dirname, '../dist'), // 打包后的文件路径
},
open: true, //自动打开浏览器
compress: true, //启动gzip压缩
port: 9000 // 端口号
},
output: {
filename: "[name].js", // 打包后的文件名称
path: path.resolve(__dirname, "../dist"), // 打包后的目录
},
plugins: htmlPlugins
};
执行npm run dev/build结果都正常
3.4 加入clean-webpack-plugin清理打包目录
使用 clean-webpack-plugin清除dist文件夹
安装 clean-webpack-plugin
npm i -D clean-webpack-plugin
接着修改webpack.config.js , 引入clean-webpack-plugin,及修改plugins
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
//...
module.exports = {
// ....
plugins: [new CleanWebpackPlugin()].concat(htmlPlugins)
}
4.引入CSS
4.1 引入css、less、scss等
先修改public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>webpack learning</title>
</head>
<body>
<noscript>
<strong>We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<h1>Hello World!</h1>
<h2>Hello World!</h2>
<h3>Hello World!</h3>
<h4>Hello World!</h4>
<h5 id="title"></h5>
</body>
</html>
我们在src里创建assets文件夹, 接着在该文件夹下创建index.css、index.less、index.scss, search.less
index.css
h1 {
color: red;
}
index.less
@color: blue;
h2 {
color: @color;
}
index.scss
$color:yellow;
h3 {
color: $color;
}
search.less
@color:wheat;
h1, h2, h3 {
color: @color;
}
修改src/index/index.js
index.js
import { greeting } from "./greeting.js";
import '../../assets/index.css';
import '../../assets/index.less';
import '../../assets/index.scss';
document.getElementById('title').textContent = greeting("world!");
由于webpack开箱即用只支持JS和JSON两种文件类型,此时我们需要利用style-loader、css-loader来解析css文件, 利用less、less-loader来解析.less文件,利用sass、sass-loader来解析.scss文件
我们一次性安装这些loader
npm i -D style-loader css-loader less less-loader sass sass-loader
接着修改webpack.config.js
//...
module.exports = {
// ....
module:{
rules:[
{
test:/\.css$/,
use:['style-loader','css-loader'] // 从右向左解析原则
},
{
test:/\.less$/,
use:['style-loader','css-loader','less-loader'] // less的loader
},
{
test:/\.scss$/,
use:['style-loader','css-loader','sass-loader'] // scss的loader
}
]
}
}
执行npm run dev,可以看出引入的样式文件都以style的方式引入到html页面中
4.2 拆分css成独立的文件
webpack 4.0以前,我们通过extract-text-webpack-plugin
插件,把css样式从js文件中提取到单独的css文件中。webpack4.0以后,官方推荐使用mini-css-extract-plugin
插件来打包css文件
安装mini-css-extract-plugin
npm i -D mini-css-extract-plugin
修改配置如下:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
//...
module.exports = {
// ....
plugins: [
new CleanWebpackPlugin(),
...htmlPlugins,
new MiniCssExtractPlugin({
filename: "[name].[hash].css",
chunkFilename: "[id].css",
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"], // 从右向左解析原则
},
{
test: /\.less$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"],
},
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
}
]
}
}
执行npm run build,css被打包出单独成一个文件
执行npm run dev
4.3 为css添加浏览器前缀
利用postcss-loader、autoprefixer来为css添加浏览器前缀
安装postcss-loader、autoprefixer
npm i -D postcss-loader autoprefixer
为了看实际效果,我们在css文件里加animation样式:
src/index/index.less
@color: blue;
h2 {
color: @color;
}
.test {
width: 100px;
height: 100px;
background: red;
position: relative;
animation: mymove 5s infinite;
}
@keyframes mymove {
from {
left: 0px;
}
to {
left: 200px;
}
}
修改配置webpack.config.js
//...
module.exports = {
// ....
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"], // 从右向左解析原则
},
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [["autoprefixer"]],
},
},
},
"less-loader",
], // less的loader
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [["autoprefixer"]],
},
},
},
"sass-loader",
], // scss的loader
}
],
}
}
执行npm run dev 可以看到animation样式已经加了浏览器前缀
5.打包图片、媒体、字体等文件
5.1 解决引用文件路径等问题
file-loader
就是将文件在进行一些处理后(主要是处理文件名和路径、解析文件url),并将文件移动到输出的目录中
安装file-loader
npm i -D file-loader
在assets里引入test.jpg、test1.jpg、movie.ogg等资源
修改src/index/index.js
import { greeting } from "./greeting.js";
import '../../assets/index.css';
import '../../assets/index.less';
import '../../assets/index.scss';
// 在js中使用图片
import test from "../../assets/test.jpg";
import test1 from "../../assets/test1.jpg";
import movie from "../../assets/movie.ogg";
document.getElementById('title').textContent = greeting("world!");
let img = new Image();
img.src = test;
document.getElementById("testImg").appendChild(img);
let img1 = new Image();
img1.src = test1;
document.getElementById("testImg").appendChild(img1);
let videoDom = document.createElement("video");
videoDom.src = movie;
videoDom.controls = "controls"
document.getElementById("testMedia").appendChild(videoDom);
修改配置文件,加上该loader
//...
module.exports = {
// ....
module: {
rules: [
{
test: /\.(gif|png|jpe?g)$/i,
use: [
{
loader: "file-loader",
options: {
name: "[path][name].[ext]",
},
},
],
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件
use: [
{
loader: "file-loader",
options: {
name: "[path][name].[ext]",
},
},
],
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
use: [
{
loader: "file-loader",
options: {
name: "[path][name].[ext]",
},
},
],
},
],
}
}
执行npm run build 效果如下:
5.2 优化图片等问题
url-loader 一般与file-loader搭配使用,功能与 file-loader 类似,如果文件小于限制的大小。则会返回 base64 编码,否则使用 file-loader 将文件移动到输出的目录中
安装url-loader
npm i -D url-loader
修改配置文件,
//...
module.exports = {
// ....
module: {
rules: [
{
test: /\.(gif|png|jpe?g)$/i,
use: [
{
loader: "url-loader",
options: {
limit: 1024 * 8, // // 将小于8KB的图片转换成base64的格式
fallback: {
loader: "file-loader",
options: {
name: "img/[name].[hash:8].[ext]", // 文件名.hash.文件扩展名 默认格式为[hash].[ext]
},
},
},
},
],
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件
use: [{
loader: 'file-loader',
options: {
name: 'media/[name].[ext]'
}
}]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
use: [
{
loader: "file-loader",
options: {
name: "[path][name].[ext]",
},
},
],
},
],
}
}
执行npm run dev可以看出小于8kb的图片base64编码了
6. babel转ES5
在webpack中 默认只能处理部分 ES6的新语法,一些更高级的ES6或ES7的语法,webpack是处理不了的这个时候就需要借助第三方的loader 来帮助webpack 处理这些高级的语法。
Balel 可以帮我我们将高级的语法转为低级的语法
利用babel-loader可以使我们的js代码兼容更多的环境
安装babel-loader @babel/preset-env @babel/core
npm i -D babel-loader @babel/preset-env @babel/core
修改src/index/index.js,加上一些ES6方法
//...省略部分代码
// 箭头函数
let arrowFn = () => console.log("hello babel");
arrowFn();
// 解构赋值
let arr = [1, 2, 3];
let [a, b, c] = arr;
// 拓展运算符
console.log(...arr);
//
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach((x) => s.add(x));
for (let i of s) {
console.log(i);
}
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, "done");
});
}
timeout(100).then((value) => {
console.log(value);
});
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint("hello world", 50);
修改配置文件,加上babel-loader
//...
module.exports = {
// ....
module: {
rules: [
{
test:/\.js$/,
use:{
loader:'babel-loader',
options:{
presets:['@babel/preset-env']
},
},
exclude: /node_modules/
},
],
}
}
在npm run dev执行完后发现一个错误
这个报错表面上是由于 async function 语法被 babel 转译之后的代码使用了 regeneratorRuntime 这个变量,但是这个变量在最终的代码里未定义造成的报错。
babel 在转译的时候,会将源代码分成 syntax 和 api 两部分来处理:
- syntax:类似于展开对象、optional chain、let、const 等语法
- api:类似于 [1,2,3].includes 等函数、方法
上面的babel-loader
只会将 ES6/7/8语法转换为ES5语法,但是对新api并不会转换 例如(promise、Generator、Set、Maps、Proxy等)
此时我们需要借助babel-polyfill来帮助我们转换
安装babel-polyfill
npm i @babel/polyfill
接着在src/index/index.js引入该文件
import "@babel/polyfill";
// ...省略以下代码
执行打包,一切正常,但在这种模式下,babel 会将所有的 polyfill 全部引入导致打包体积过大,而且需要在头部引入该文件,正确的做法是使用按需加载,@babel/preset-env 中有一个配置选项 useBuiltIns,用来告诉 babel 如何处理 api。将 useBuiltIns 改为 "usage",babel 就可以按需加载 polyfill,并且不需要手动引入 @babel/polyfill
修改配置文件:
//...
module.exports = {
// ....
module: {
rules: [
{
test:/\.js$/,
use:{
loader:'babel-loader',
options:{
presets:['@babel/preset-env']
},
},
exclude: /node_modules/
},
],
}
}
同时去掉src/index/index.js中import "@babel/polyfill";
执行npm run dev看下打包完的index.js
可以看出是按需加载的
参考资料:
https://juejin.cn/post/6844904031240863758#heading-10