webpack学习
简介
https://www.webpackjs.com/
打包多个文件资源成一个,避免全局变量污染,单个文件中可以用模块规范(esm) import, export
安装
npm init -y
npm install webpack --dev
npm install webpack-cli
npm install -D webpack-dev-server // –save-dev 简写 -D
配置webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// 配置环境(development /production /none),默认生产
mode: "development",
// 配置入口
// 多入口,打包成多个文件,一些暂时用不到的可以先不加载,优化首屏渲染
// entry: "./main.js", // 可配置多个
entry: {
index: './src/index.js',
another: './src/another-module.js'
}
// 输出
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js', // 多入口输出可以自动注入'[name].bundle.js'
clean: true, // webpakc5可以将dist文件夹清空重新生成
},
// 优化首屏,除了多入口,多入口时 也可以spliteChunks自动抽取重复代码,自动拆分;
optimization: { // 选项
splitChunks: { // 自动抽取重复代码,单独dist里放一个文件 vendors-node_modiles_xxx
chunks: 'all'
}
}
// loader, 加载器,文件翻译转换,翻译css,scss等成js文件
module: {
// 匹配不同的文件 进行解析
rules: [
{
test: /\.css$/i, // 匹配规则
// .css 转 .js;源码->css-loader->style-loader->webpack
use: ['style-loader','css-loader'], // require加载css-loader库,从后向前执行
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: "asset/resource" // 不需要库,webpack5以上支持
},
{
test: /\.json5$/i,
type: "json",
parser: {
parse: json5.parse,
}
}
]
},
// plugins
plugins: [
new HtmlWebpackPlugin({ // 可以输出index.html文件,也可以直接打开
title: "Output Management"
})
],
// 开发时,生成服务器,也可以设置devTool
devServer: {
static: "./dist"
}
}
package.json配置
"scripts": {
"build": "webpack",
"dev": "webpack-dev-server --mode development --hot" // 热更新 webpack serve
}
多入口时的懒加载
async和await实现,比如点击按钮才加载page2
btn.onclick = async () => {
const page2 = await import('./page2.js')
console.log(page2())
}
tree shaking(摇树优化)
tree shaking 是起源于rollup,在webpack v2中被用上。如果mode:production,默认开启tree shaking
定义:一个模块中可能有多个方法,打包时会打包所有,tree shaking 就是只把用到的方法打入 bundle ,没用到的在 uglify 阶段擦除。
ugligy:uglifyjs-webpack-plugin,是 webpack 内置的,用于压缩 js 文件,可开启并行压缩。
DCE 死码消除(Dead code elimination):代码不会被执行 if(false){}、执行结果没用到 (函数返回没用)、只写不读 (变量定义了没用)
要求是必须 ES6 语法,CJS 不支持 如 require。 因为 tree shaking 是对代码的静态分析, 而 require 可以动态引入,运行时无法分析 哪些代码用到了。
如将 mode:none,可测试,按需引入( import {} ) 时,没引入的也被加入打包了。
文件指纹 index_51727db.js
即打包后的文件后缀(如index后的6位后缀),用于版本管理,判断文件改变;也可以对没用修改的文件启用浏览器缓存。
指纹生成:Hash(只有有文件修改,整个文件hash都会变), ChunkHash(和webpack打包的chunk有关,不同entry生成不同ChunkHash), ContentHash(根据文件内容定义,随着文件内容改变)
指纹设置:图片、字体用hash,在file-loader中设置 name;使用chunkhash 设置 output 的 filename;css文件使用 contenthash,用插件将css提取出独立文件,如下:
如 loader 中设置 hash,采用md5,因为 md5 有32 位,我们可以截取前8位 [hash:8],如下:
文件压缩 xx.min.js
js 用 uglifyjs插件;
css 用 optimize-css-assets-webpack-plugin,同时使用 cssnano(预处理器,需要安装 ),css-loader已经不支持;
// 安装
npm i optimize-css-assets-webpack-plugin -D
npm i cssnano -D
// 引入
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
html 压缩,必用 html-webpack-plugin,可设置压缩参数 minify(如处理空格、换行等),很强大:(一个页面需要对应一个HtmlWebpackPlugin)
打包 库和组件
打包应用
dist/ bundle.js 文件结构:
将入口文件的main.js打包成main.code,然后用webpack.require()包起来,其次将依赖的js文件依次包起来
webpakc除了打包应用,还可以打包 js 库
打包 组件和库 用 rollup 更合适,更纯粹,但是webpack功能更强大,
如下面试题,实现一个大整数加法打包库:(完整版见 D:\workspace\webpackDemo\geektime-webpack-course\code\chapter03\large-number)
(要求:1. 需要打包压缩版和非压缩版,2. 支持 AMD/ CJS/ ESM 模块引入)该库目录结构:
// webpack.config.js
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
entry: {
'large-number': './src/index.js',
'large-number.min': './src/index.js'
},
output: {
filename: '[name].js',
library: 'largeNumber', // 打包出来库的名称
libraryTarget: 'umd', // 可以用多种方法引用
libraryExport: 'default'
},
mode: 'none', // 去掉文件压缩
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({ // 针对制定文件压缩,基于 uglify 改造的
include: /\.min\.js$/, // 正则
})
]
}
// index.js
if (process.env.NODE_ENV === 'production') {
module.exports = require('./dist/large-number.min.js');
} else {
module.exports = require('./dist/large-number.js');
}
SSR
https://time.geekbang.org/course/detail/100028901-103716
- 打包时若出现: window is not defined报错,需要配置 hack
// 入口文件 index.js
if (typeof window === 'undefined') {
global.window = {};
}
- 若出现如下:
则需要 在源代码 中用CJS方式引入
// import React from 'react';
// import largeNumber from 'large-number';
// import logo from './images/logo.png';
// import './search.less';
const React = require('react');
const largeNumber = require('large-number');
const logo = require('./images/logo.png');
const s = require('./search.less');
- css无法加载
样式内联 或者 模板插入
// index.html
<div id="root"><!--HTML_PLACEHOLDER--></div>
// index.js
const renderMarkup = (str) => {
const dataStr = JSON.stringify(data);
return template.replace('<!--HTML_PLACEHOLDER-->', str);
}
与vite区别
bundle 和 bundless,vite 是 bundless,浏览器直接请求模块
资源加载的差异 如下:
bundle (webpack 5)会在开始加载一个bundle.js; 而 bundless (vite) 会加载 App.vue。
-
打包:webpack将各个模块按规则合并,较大的依赖打包代价高。而vite不进行打包,充分利用浏览器,import的部分直接浏览器类似 get请求 拿url(如http://localhost:3000/src/main.js)下的资源
-
热更新:webpack虽然能定位模块,但是编辑文件后依然将重新构建文件。而vite可以利用浏览器header加速,源码利用304协商缓存,依赖会强缓存
如下:webpack的 HMR 需要重新生成 bundle.js文件,而vite 直接去请求修改的文件(模块)
-
vite缺点:生产环境构建用Rollup(esbuild对于css和代码分割不是很友好)
打包流程:
https://time.geekbang.org/course/detail/100028901-413936
利用 serveStaticPlugin 将整个项目根目录和public设为静态目录;
利用koa-etag 打 etag, 如果文件变换,通过 etag 通知变化内容;
启devServer,静态文件托管;
重写模块路径,bare import 需要重写(裸导入 import vue from ‘vue’),因为浏览器只识别 相对/ 绝对路径,重写规则如下(/@modules,同时补齐扩展名):
静态资源打包,如下:
vue 脚本打包,如下:
通过 compiler-sfc 包解析,compilerSfc.parse只能解析 js,style和template需要别的处理
template 模板打包 用 @vue/compiler-dom 编译 template,然后返回给浏览器: