Webpack5不完全指南-进阶篇
处理HTML中的静态资源
前面处理了css中的图片资源,webpack默认不会对HTML进行解析,当然也不会收集处理HTML中的图片等资源,html-withimg-loader可以解析HTML中图片资源收集到 dist/img 中
安装
npm i html-withimg-loader -D
配置loader
// webpack.config.js module: { rules: [ { test: /\.(htm|html)$/i, loader: "html-withimg-loader" } ] 复制代码
多页面应用打包
基于MVVM的开发的web应用多为单页面应用,但是某些场景更需要多页面应用,例如OA办公系统等,webpack也可以实现多页面应用打包。
在src下新建入口文件 main.js 和 other.js
// src/main.js console.log("这是main.js") // src/other.js console.log("这是other.js") 复制代码
配置入口和出口
// webpack.custom.config.js module.exports = { // entry: "./src/index.js", // 1. 当为多组入口出口时 传入对象 entry: { // 2. 注册多个入口文件 index: "./src/main.js", other: "./src/other.js", }, // 项目出口配置 output: { path: path.resolve(__dirname, "./dist"), // filename: 'bundle.js' // 3. 当为多个出口文件时 文件名要用 name 变量 否则会报错 filename: '[name].js' } }, 复制代码
执行
npm run build
打包项目
配置多个html页面
前面只是配置了多个入口和出口,所以依赖最终还是在一个html这里我们还需要拆分多个html
新建 src/other.html
配置插件
plugins: [ // 新建一个HtmlPlugin实例 new HtmlPlugin( { // 打包输出文件名 filename: "index.html", // 被复制的目标html template: "./src/index.html", // 默认引入所有chunks(index.js和other.js),指定要引入的chunks chunks: ["index"] } ), new HtmlPlugin( { filename: "other.html", template: "./src/other.html", // 默认引入所有chunks(index.js和other.js),指定要引入的chunks chunks: ["other"] } ), ] 复制代码
npm run build
重新打包
第三方库的引入方式
webpack的原理是将每个模块作为闭包来进行打包,局部模块导入的库只是局部的变量无法全局使用,可以借助 expose-loader 实现全局作用域变量注入,也可以通过内置插件 webpack.ProvidePlugin
对每个闭包作用域注入变量,自动加载模块
示例
以jquery为例
安装
npm i jquery
在 main.js 中使用jquery
console.log("这是main.js"); import $ from 'jquery' // 在当前作用域导入并访问JQ console.log("JQ", $); // 访问全局作用域下的JQ console.log("window.jquery", $); $("body").css("backgroundColor", "green") 复制代码
npm run build
重新构建后打开 index.html正如之前所讲,使用webpack,在当前模块导入的库只能作为局部变量使用。
expose-loader
安装
npm i expose-loader -D
配置loader
// webpack.custom.config.js module: { rules: [ // 注入全局变量 { // require.resolve 解析jquery库的绝对路径 test: require.resolve('jquery'), loader: 'expose-loader', options: { // 使用变量 $ 挂载JQ exposes: '$', } } ] }, 复制代码
npm run build
重新打包,可以访问到全局变量 $
webpack.ProvidePlugin
ProvidePlugin是webpack内置插件,可以为每个模块注入变量
修改 main.js、other.js,在 other.js 不导入直接使用JQ
// main.js console.log("这是main.js"); // other.js console.log("这是other.js"); $("body").css("backgroundColor", "pink") 复制代码
配置plugin
plugins: [ new HtmlPlugin( { filename: "index.html", template: "./src/index.html", // 1.index.html 引入所有js chunks: ["index", "other"] } ), // 2.使用 Webpack.ProvidePlugin 为每个模块注入局部变量 new Webpack.ProvidePlugin({ // 每个模块导入jquery模块,并挂载到$、jQuery变量上 $: "jquery", jQuery: "jquery" }) ] 复制代码
区分环境配置文件
项目开发时,一般需要使用两套配合文件,开发阶段打包(不压缩代码、不优化代码、注重效率)和生产阶段打包(压缩代码、优化代码、打包后上线直接使用)
我们抽离为三个配置文件:
webpack.base.js
:所有基础、公共的配置放在该配置文件中
webpack.prod.js
:生产环境的单独配置放在该配置文件中放在该配置文件中
webpack.dev.js
:开发环境的单独配置放在该配置文件中放在该配置文件中
操作流程
将开发环境和生产环境的公共配置(入口、出口、loader等)放到
webpack.base.js
// webpack.base.js const path = require("path") const HtmlPlugin = require("html-webpack-plugin") const CleanWebpackPlugin = require("clean-webpack-plugin").CleanWebpackPlugin const CopyWebpackPlugin = require("copy-webpack-plugin") const Webpack = require("webpack") module.exports = { entry: { index: "./src/main.js", other: "./src/other.js", }, output: { path: path.resolve(__dirname, "./dist"), filename: '[name].js' }, plugins: [ new HtmlPlugin( { filename: "index.html", template: "./src/index.html", chunks: ["index", "other"] } ), new HtmlPlugin( { filename: "other.html", template: "./src/other.html", chunks: ["other"] } ), new CleanWebpackPlugin(), new CopyWebpackPlugin({ patterns: [{ from: path.join(__dirname, "assets"), to: 'assets' }] }), new Webpack.BannerPlugin("这是一段版权信息"), new Webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery" }) ], module: { rules: [ { test: /\.css$/i, use: ['style-loader', 'css-loader'] }, { test: /\.less$/i, use: ['style-loader', 'css-loader', 'less-loader'] }, { test: /\.jpg|png|gif|bmp|ttf|eot|svg|woff|wpff2$/i, type: "asset", parser: { dataUrlCondition: { maxSize: 100 } }, generator: { filename: 'img/[name].[hash:6][ext]', publicPath: './' } }, { test: /\.js$/i, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], plugins: ["@babel/plugin-transform-runtime"] } } }, { test: /\.(htm|html)$/i, loader: "html-withimg-loader" } ] } } 复制代码
安装 webpack-merge, 在
prod
和dev
中,使用webpack.merge
对base
导出的配置项进行合并导出npm i webpack-merge -D
配置 dev、prod
// webpack.dev.js // 1.导入公共webpack配置 const baseConfig = require("./webpack.base.js") // 2.导入合并webpack配置项函数 const merge = require("webpack-merge").merge console.log("merge", merge); // 3.合并配置项 const devConfig = merge(baseConfig, { mode: "development", devServer: { open: true, port: 3000, compress: true, hot: true, // 服务的基准路径 // contentBase: "./src" }, devtool: "eval-cheap-module-source-map" }) module.exports = devConfig 复制代码
// webpack.prod.js // 1.导入公共webpack配置 const baseConfig = require("./webpack.base.js") // 2.导入合并webpack配置项函数 const merge = require("webpack-merge").merge // 3.合并配置项 const prodConfig = merge(baseConfig, { mode: "production", // 4.生产环境谨慎使用source-map // devtool: "cheap-module-source-map" }) module.exports = prodConfig 复制代码
重新配置package.json中,开发和生产环境的脚本指令
"scripts": { // 生产环境使用 webpack.prod.js 进行打包 "build": "npx webpack --config webpack.prod.js", // 开发环境使用 webpack.dev.js 进行打包 "dev": "npx webpack server --config webpack.dev.js", }, 复制代码
配置文件归类
前面根据环境配置了多个配置文件导致根目录文件过多,为了让项目结构清晰方便管理 我们要对配置文件归类处理
将所有Webpack配置文件放到 config 文件夹
修改 package.json 中加载配置文件的路径
"scripts": { "build": "npx webpack --config ./config/webpack.prod.js", "dev": "npx webpack server --config ./config/webpack.dev.js" } 复制代码
目录变更,修改配置文件的绝对路径
注意:配置文件的相对路径都是相对于 项目根路径!!!// config/webpack.base.js module.exports = { entry: { // 配置文件中的相对路径都是相对于 项目根目录而非当前文件 index: "./src/main.js", other: "./src/other.js", }, output: { // 1.配置文件中绝对路径是相对于当前文件 所以必须要修改 path: path.resolve(__dirname, "..", "./dist"), filename: '[name].js' }, plugins: [ new CopyWebpackPlugin({ patterns: [{ // 2.配置文件中绝对路径是相对于当前文件 所以必须要修改 from: path.join(__dirname, "..", "assets"), to: 'assets' }] }), ] } 复制代码
定义环境变量
在某些场景下需要定义环境变量,例如根据环境区分接口地址(开发阶段使用内网地址或本机地址)
声明开发阶段、生产阶段的环境变量
// webpack.dev.js const webpack = require("webpack") const devConfig = merge(baseConfig, { plugins: [ /** webpack.DefinePlugin(options):声明变量 * options:配置对象 * - key:变量名 * - value:值,表达式 */ new webpack.DefinePlugin({ // 开发环境变量IS_DEV = true IS_DEV: 'true', // 变量test = 2 test:'1+1', // 变量test1 = "1+1" test1:'"1+1"' }) ], }) // webpack.prod.js const webpack = require("webpack") const prodConfig = merge(baseConfig, { plugins: [ new webpack.DefinePlugin({ // 生产环境变量IS_DEV = false IS_DEV: 'false', test: '1+1', test1: '"1+1"' }) ], }) 复制代码
根据环境变量动态设置 接口基准地址
// src/main.js console.log("这是main.js"); // 根据环境设置接口基准地址 let BASEURL = IS_DEV ? "http://127.0.1:3000" : "http://www.baidu.com" console.log("环境变量", BASEURL, IS_DEV, test, test1); 复制代码
开发环境/生产环境打包结果
通过ajax代理解决跨域问题
由于客户端浏览器的同源策略问题,项目开发阶段访问源之外的接口会出现跨域问题,通过 devSever 配置代理可以解决跨域问题
// webpack.dev.js const devConfig = merge(baseConfig, { mode: "development", devServer: { open: true, port: 3000, compress: true, hot: true, proxy: { // 当发送的ajax请求地址以'/api'开头时,devServer会代理发送一个以'https://www.baidu.com'开头ajax请求 // /api/index.do ==> https://www.baidu.com/idnex.do '/api': { // 代理到哪个接口 target: 'https://www.baidu.com', // 改变接口的源 changeOrigin: true, ws: true, // 重写目标接口 pathRewrite: { '^/api': '' } } } // 服务的基准路径 // contentBase: "./src" }, }) 复制代码
模块热更新(HMR)
在开发阶段,devServer会监听代码如果代码更新会重新加载整个页面这会导致几个问题
重新加载页面时丢失的应用程序状态
其他未修改的地方也随着页面刷新重新加载
模块热更新(HMR)不会重新打包整个项目而是以补丁的形式进行局部的更新
修改为单入口文件
必须为单入口文件才能使用热更新// webpack.base.js module.exports = { entry: { index: "./src/main.js", // other: "./src/other.js" } } 复制代码
新建 src/hotModule.js
export default "这是热更新模块" 复制代码
监听 hotModule.js
import str from './hotModule.js' console.log(str); // console.log("这是main.js"); if (module.hot) { module.hot.accept("./hotModule.js", function () { const hotModule = require("./hotModule.js") // 当hotModule 内容更新时触发 console.log("hotModule更新了+++++++++++++++++++++++++++++", hotModule); }) } 复制代码
npm run dev
执行脚本 并手动更新src/hotModule.js
作者:_光光
链接:https://juejin.cn/post/7017719175380467720