插件 webpack-bundle-analyzer
生成代码分析报告
webpack 实战
- yarn init -y
- yarn add webpack
- pnpm install webpack webpack-cli -D
webpack 可以是 0 配置
手动配置
- 默认配置文件得名字是 webpack.config.js
yarn add less less-loader style-loader -D
pnpm i css-loader style-loader -D
pnpm i mini-css-extract-plugin -D # 抽离css插件
# 自动添加前缀
pnpm i postcss-loader autoprefixer -D
# css压缩
# npmjs.com/package/mini-css-extract-plugin
pnpm i optimize-css-assets-webpack-plugin -D
# 如果用了上面插件就需要使用
pnpm i uglifyjs-webpack-plugin -D
#
将 es6 转换成 ex5
pnpm i babel-loader @babel/core @babel/preset-env -D
pnpm add @babel/polyfill
代码检测工具
https://eslint.org/
pnpm i eslint eslint-loader
第三方模块
pnpm i jquery
expose-loader 暴露全局的 loader 内联 loader
pnpm i add expose-loader;
import $ from "expose-loader?$!jquery";
window.$;
同上
rules:[
{
test: require.resolve("jquery"),
use: "expose-loader?$",
},
]
全局注入
const webpack = require("webpack");
module.exports = {
plugins: [
new webpack.ProvidePlugin({
$: "jquery", //每个模块中都注入
}),
],
};
总结
- expose-loader 暴漏到 window
- providePlugin 给每个提供一个$
- 引入不打包
webpack 打包我们的图片
- 在 js 中创建图片来引入
- 在 css 引入 background('url')
// pnpm i file-loader -D 默认会在内部生成一张图片到build目录下
// 把生成的图片的名字返回
// rules:[{
// test:/\.(png|jpg|gif)$/,
// use:'file-loader'
// }]
import logo from "./logo.png"; // 把图片引入,返回的结果是一个新的图片地址
let image = new Image();
image.src = logo; // 就是一个普通的字符串
document.body.appendChild(image);
body {
background: red;
background: url(require("./logo.png"));
/* css-loader 默认是支持的 */
background: url("./logo.png");
}
pnpm i html-withimg-loader -D 解决 html 的图片问题
rules:[
{
text:/\.html$/,
use:'html-withimg-loader',
}
]
<img src="./logo.png" alt =""/>
将小图片变成 base64
- url-loader
rules:[
{
text:/\.(png|jpg|gif)$/,
<!-- 做一个限制 当图片小于多少k用base64 -->
<!-- 否则使用file-loader 产生真实的图片 -->
use:{
loader:'url-loader',
options:{
limit:200 *1024
}
},
}
]
<img src="./logo.png" alt =""/>
多页面应用
// webpack.config.js
let path = require("path");
let HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
mode: "development",
entry: {
home: "./src/index.js",
other: "./src/index.js",
},
output: {
// [name] home other
filename: "[name].[hash].js",
path: path.resolve(__dirname, "dist"),
},
plugins: [
// html-webpack-plugin -D 用来使用模板生成html
new HtmlWebpackPlugin({
teamplate: "./index.html",
filename: "home.html",
chunks: ["home"],
}),
new HtmlWebpackPlugin({
teamplate: "./index.html",
filename: "other.html",
chunks: ["other", "home"],
}),
],
};
webpack 中的一些小插件
- cleanWebpackPlugin new CleanWebpackPlugin('./dist')
- copyWebpackPlugin
new copyWebpackPlugin([{from:'./doc',to:'./'}])
- bannerPlugin 内置 版权声明插件
let webpack = require("webpack");
new webPack.BannerPlugin("make 2019");
webpack 跨域问题
// server.js
let express = require("express");
let app = express();
app.get("/api/use", (req, res) => {
res.json({ name: "xxx" });
});
app.listen(3000);
// index.js
let xhr = new XMLHttpRequest();
xhr.open("GET", "/api/user", true);
xhr.onload = function () {
console.log(xhr.response);
};
xhr.send();
const devServer = {
proxy: {
//1
// '/api':'http://localhost:3000'
//2
// '/api':{
// target:'http://localhost:3000',
// pathRewrite:{//重写的方式把请求代理到其他服务器
// '/api':'',
// },
// }
//3 模拟数据
// before(app) {
// //提供的方法
// app.get("/api/use", (req, res) => {
// res.json({ name: "xxx" });
// });
// },
},
};
4 有服务端 不用用代理来处理,能不能再服务端启动 webpack 端口用服务端端口
- yarn add webpack-dev-middleware webpack -D 开发服务的中间件
// server.js
const express = require("express");
const app = express();
const webpack = require("webpack");
const middle = require("webpack-dev-middleware");
let config = require("./webpack.config.js");
const compiler = webpack(config);
app.use(middle(compiler));
app.get("/user", (req, res) => {
res.json({ name: "xx" });
});
app.listen(3000);
// 直接运行 node server.js
开发和构建配置区分开
- yarn add webpack-merge -D
webpack.base.js
//
webpack.dev.js
const { smart } = require("webpack-merge");
let base = require("./webpack.base.js");
module.exports = smart(base, {
mode: "development",
devServer: {},
devtool: "source-map",
});
webpack.prod.js
const { smart } = require("webpack-merge");
let base = require("./webpack.base.js");
module.exports = smart(base, {
mode: "production",
optimization: {
minimizer: {},
},
plugins: [],
});
npm run build -- --config webpack.dev.js
webpack 优化
const rules = [
{
test: /\.js$/,
exclude: /node_modules/, //排除node_modules
include: path.resolve("src"), //只找src目录
use: {
loader: "babel-loader",
},
},
];
moment 时间插件
- yarn add moment
import moment from "moment";
// 设置语言
moment.locale("zh-cn");
//距离现在有多少天
let r = moment().endOf("day").fromNow();
console.log(r);
- 手动引入所需要的语言包
import "moment/locale/zh-cn";
moment.locale("zh-cn");
const webpack = require("webpack");
const plugins = [
new webpack.IgnorePlugin(/\.\/locale/, /moment/), //引入的时候忽略掉
];
动态链接库
- yarn add react react-dom
import React from "react";
import { render } from "react-dom";
render(<h1>jsx</h1>, window.root);
<body>
<div id="root"></div>
<!-- 直接使用动态链接库,先打包动态链接库 -->
<script src="/_dll_react.js"></script>
</body>
- webpack.config.js
module.exports = {
devServer: {
port: 3000,
open: true,
contentBase: "./dist",
},
rules: [
{
text: /\.js$/,
exclude: /node_modules/,
include: path.resolve("src"),
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
},
},
},
],
plugins: [
new webpack.DllReferencePlugin({
mainifest: path.resolve(__dirname, "dist", "manifest.json"),
}),
],
};
- webpack.config.react.js
let path = require("path");
let webpack = require("webpack");
module.exports = {
mode: "development",
entry: {
// test: "./src/test.js",
react: ["react", "react-dom"],
},
output: {
// filename: "[name].js",
filename: "_dll_[name].js", //产生的文件名
path: path.resolve(__dirname, "dist"),
// library: "a", //默认打包结果给一个变量 var a = 结果
library: "_dll_[name]", //_dll_react
libraryTarget: "var",
libraryTarget: "commonjs", //结果放exports属性上 exports['a']=结果
libraryTarget: "umd", //umd模式
},
plugins: [
new webpack.DllPlugin({
//name == library
name: "_dll_[name]",
path: path.resolve(__dirname, "dist", "manifest.json"), //清单
}),
],
};
// npx webpack --config webpack.config.react.js
多线程打包
let webpack = require("webpack");
//模块 happypack 可以实现多现成打包
// yarn add happypack
let Happypack = require("happypack");
module.exports = {
module: {
noParse: /jquery/, //不去解析jquery中的依赖库
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
include: path.resolve("src"),
use: "Happypack/loader?id=js", //打包js
},
{
test: /\.css$/,
use: "Happypack/loader?id=css",
// use: ["style-loader", "css-loader"],
},
],
},
plugins: [
new Happypack({
id: "css",
use: ["style-loader", "css-loader"],
}),
new Happypack({
id: "js",
use: [
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
},
},
],
}),
],
};
webpack 自带的优化功能
let add = (a, b) => {
return a + b + "add";
};
let minus = (a, b) => {
return a - b + "minus";
};
export default {
sum,
minus,
};
import calc from "./test";
// 默认import 在生产环境下 会自动去除掉没有使用的代码-tree-shaking
// require('.test')
// es6模块会把结果放到 default
console.log(calc.default.add(1, 2));
// console.log(calc.add(1, 2));
// scope hosting 作用域提升
let a = 1;
let b = 2;
let d = a + b; // 在webpack中自动省略可以简化的代码
console.log(d, "----");
// production下
// 打包成 console.log(3);
抽取公共代码
module.exports = {
optimization: {
//commonChunkPlugins
// minizer:{}//压缩的
splitChunks: {
//分割代码块
cacheGroups: {
//缓存组
common: {
chunks: "initial", //刚开是就抽离 不考虑异步
//公共的模块
miniSize: 0, //大小大于0 就抽离
// 使用多少次才抽离
miniChunks: 2,
},
// 第三方抽离
vendor: {
priority: 1, //权重 先抽离第三方模块 再抽离上面的
test: /node_modules/, //把你抽离出来
chunks: "initial",
minSize: 0,
minChunks: 2,
},
},
},
},
mode: "production",
entry: {
index: "./src/index.js",
other: "./src/other.js",
},
deServer: {
port: 3000,
open: true,
contentBase: "./dist",
},
output: {
filename: "[name].js",
path: path.resolve(__dirname, "dist"),
},
};
懒加载
// source.js
export default "xxx";
let button = document.createElement("button");
button.innerHTML = "hhh";
button.addEventListener("click", function () {
console.log("click");
// es6 草案中的语法 jsonp实现动态加载文件
// yarn add @babel/plugin-syntax-dynamic-import -D
import("./source.js")。then(data=>{
console.log(data.default);
});
});
document.body.appendChild(button);
const rules = [
{
test: /.\js$/,
exclude: /node_modules/,
include: path.resolve("src"),
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
plugins: ["@babel/plugin-syntax-dynamic-import"],
},
},
},
];
热更新
module.exports = {
mode: "production",
entry: {
index: "./src/index.js",
},
devServer: {
hot: true, //启用热更新
port: 3000,
open: true,
contentBase: "./dist",
},
plugins: [
new webpack.NameModulesPlugin(), //打印更新的模块路径 告诉哪个模块更新了
new webpack.HotModuleReplacementPlugin(), //热更新插件
],
};
// index.js
import str from "./source";
console.log(str);
if (module.hot) {
module.hot.accept("./source", () => {
console.log("文件更新了");
let str = require("./source");
console.log(str);
});
}
webpack 的 tapable
Webpack 本质上是一种时间流的机制,它的工作流就是将各个插件串联起来,而实现的一切的核心就是 Tapable,Tapable 有点类似于 nodejs 的 events 库,核心原理也是依赖于发布订阅模式。

- yarn add tapable
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook,
} = require("tapable");
let { SyncHook } = require("tapable");
class Lesson {
constructor() {
this.hooks = {
arch: new SyncHook("name"),
};
}
tap() {//注册监听函数
this.hooks.arch.tap("node", function (name) {
console.log("node", name);
});
this.hooks.arch.tap("react", function (name) {
console.log("react", name);
});
start(){
this.hooks.arch.call('jw')
}
}
}
const l = new Lesson()
l.tap() // 注册两个事件
l.start(); // 启动钩子
- SyncHook 同步的钩子
class SyncHook {
//构子的同步问题
constructor(args) {
//args=>['name']
this.tasks = [];
}
tap(name, task) {
this.tasks.push(task);
}
call(...args) {
this.tasks.forEach((task) => task(...args));
}
}
let hook = new SyncHook(["name"]);
hook.tap("react", function (name) {
console.log("react", name);
});
hook.tap("node", function (name) {
console.log("node", name);
});
hook.call("jw");
- SyncBailHook 保险钩子
class SyncBailHook {
//构子的同步问题
constructor(args) {
//args=>['name']
this.tasks = [];
}
tap(name, task) {
this.tasks.push(task);
}
call(...args) {
let ret;
let index = 0; //当前要执行第一个
do {
ret = this.tasks[index++](...args);
} while (ret === undefined && index < this.tasks.length);
}
}
let hook = new SyncBailHook(["name"]);
hook.tap("react", function (name) {
console.log("react", name);
return "停止执行"; //如果返回的不是undefined 就停止执行
});
hook.tap("node", function (name) {
console.log("node", name);
});
hook.call("jw");
- SyncWaterfallHook 瀑布函数结果传到下一个
class SyncWaterfallHook {
//构子的同步问题
constructor(args) {
//args=>['name']
this.tasks = [];
}
tap(name, task) {
this.tasks.push(task);
}
call(...args) {
let [first, ...others] = this.tasks;
let ret = first(...args);
others.reduce((a, b) => {
return b(a);
}, ret);
}
}
let hook = new SyncWaterfallHook(["name"]);
hook.tap("react", function (name) {
console.log("react", name);
return "reactok"; //如果返回的不是undefined 就停止执行
});
hook.tap("node", function (name) {
console.log("node", name);
return "node ok";
});
hook.tap("webpack", function (name) {
console.log("node", name);
return "";
});
hook.call("jw");
- SyncLoopHook 同步遇到某个不反悔 underfined 的监听函数会多次执行
class SyncLooplHook {
//构子的同步问题
constructor(args) {
//args=>['name']
this.tasks = [];
}
tap(name, task) {
this.tasks.push(task);
}
call(...args) {
this.tasks.forEach((task) => {
let ret;
do {
ret = task(...args);
} while (ret !== undefined);
});
}
}
let hook = new SyncLooplHook(["name"]);
let total = 0;
hook.tap("react", function (name) {
console.log("react", name);
return ++total == 3 ? undefined : "继续";
});
hook.tap("node", function (name) {
console.log("node", name);
});
hook.tap("webpack", function (name) {
console.log("node", name);
return "";
});
hook.call("jw");
- 异步的钩子 (串行)并行 需要等待所有并发的异步事件执行后在执行回调方法
- 同时发送多个请求
- 执行方法为 tap 注册(同步) tapAsync 注册
- tapable 库中有三种注册方法 tap 同步注册 tapAsync(cb) tapPromise(注册的是 promise)
- 调用方法 call callAsync promise
let { AsyncParallelHook } = require("tapable");
class Lesson {
constructor() {
this.index = 0;
this.hooks = {
arch: new AsyncParallelHook(["name"]),
};
}
tap() {
this.hooks.arch.tap("node", (name, cb) => {
console.log("node", name);
setTimeout(() => {
console.log("node", name);
cb();
}, 1000);
});
this.hooks.arch.tap("react", (name, cb) => {
setTimeout(() => {
console.log("react", name);
cb();
}, 1000);
});
}
start() {
this.hooks.arch.callAsync("jw", function () {
console.log("end");
});
}
}
- AsyncParralleHook 异步并行的钩子
class AsyncParralleHook {
constructor(args) {
this.tasks = [];
}
tapAsync(name, task) {
this.tasks.push(task);
}
callAsync(...args) {
//promise.all
let finalCallback = args.pop(); //拉出最终的函数
let index = 0;
const done = () => {
index++;
if (index === this.tasks.length) {
finalCallback();
}
};
this.tasks.forEach((task) => {
task(...args, done);
});
}
}
let hook = new AsyncParralleHook(["name"]);
let total = 0;
hook.tapAsync("react", function (name, cb) {
console.log("react", name);
cb();
});
hook.tapAsync("node", function (name, cb) {
console.log("node", name);
cb();
});
hook.callAsync("jw", function () {
console.log("end");
});
let { AsyncParallelHook } = require("tapable");
class Lesson {
constructor() {
this.index = 0;
this.hooks = {
arch: new AsyncParallelHook(["name"]),
};
}
tap() {
this.hooks.arch.tapPromise("node", (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("node", name);
resolve();
}, 1000);
});
});
}
}
- AyncParralleBailHook(); 带保险的异步并发的钩子
串行的钩子
let { AsyncSeriesHook } = require("tapable");
class Lesson {
constructor() {
this.index = 0;
this.hooks = {
arch: new AsyncSeriesHook(["name"]),
};
}
tap() {
this.hooks.arch.tapAsync("node", (name, cb) => {
setTimeout(() => {
console.log("node", name);
cb();
}, 1000);
});
this.hooks.arch.tapAsync("react", (name, cb) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("react", name);
cb();
});
});
});
}
start() {
this.hooks.arch.callAsync("jw").then(function () {});
}
}
npm link
// 当前这个包 放到全局下,生成一个命令
npm link
npm link zf-pack //将全局的包映射到本地
npx zf-pack //npx webpack