第四节:Webpack本地服务器搭建、剖析devServer的HRM热替换和其它配置、resolve模块解析、区分开发/生产环境方案实战
一. webpack本地服务器搭建
1. 为什么要搭建本地服务器?
我们之前通过【npm run build】,编译相关的代码; 然后需要在浏览器中运行,每次修改修改代码,都需要重新编译运行,很麻烦。我们希望可以做到,当文件发生变化时,可以自动的完成 编译 和 展示 。
2. watch mode模式
(1). 简介
在该模式下,webpack依赖图中的所有文件,只要有一个发生了更新,那么代码将被重新编译;我们不需要手动去运行 npm run build指令了;
(2). 配置
方法1:babel.config.js增加下面配置
module.exports = { mode: "development", watch:true, //开启监听 entry: "./src/main.js" }
方法2:在package.json中增加下面配置
"scripts": {
"build": "webpack --watch"
},
结果剖析:
通过【npm run build】打包,运行index.html,这个时候在修改一下代码,自动编译映射。
上述watch mode,可以监听到文件的变化,但是事实上它本身是没有自动刷新浏览器的功能的: 我们希望在不使用live-server(vs code插件)的情况下,可以具备live reloading(实时重新加载)的功能 ,这个使用就需要用下面的dev-server模式。
3. dev-server模式(推荐)
(1). 步骤
A. 安装loader:【npm install webpack-dev-server -D】
B. 修改babel.config.js中的配置,增加target节点 、devServer节点。
// 这里必须通过 commonjs的写法配置,不能写 ES6的写法 const path = require('path'); module.exports = { target: "web", mode: "development", // 设置模式, development 开发阶段, 会设置development, production 准备打包上线的时候, 设置production(默认) entry: "./src/main.js", //入口文件 output: { path: path.resolve(__dirname, "./build"), //打包后存放路径, 必须写绝对路径 filename: "bundle.js" //打包后的文件名称 }, devServer: { static: { directory: path.join(__dirname, 'public'), }, //表示从这个文件夹里找打包后没有找到的文件(打包是打src文件夹) }, module: { }, }
C. 修改package.json中的配置,script标签中增加serve节点。
"scripts": { "build": "webpack", "serve": "webpack serve" },
D. 运行指令【npm run serve】,然后访问 http://localhost:8080/ ,然后F12打开调试器,修改代码,发现页面自动刷新了,输出最新的结果了。
(2). 剖析
webpack-dev-server 在编译之后不会写入到任何输出文件(不会生成build文件夹),而是将 打包后 文件保留在内存中。
事实上webpack-dev-server使用了一个库叫memfs(memory-fs webpack自己写的) 。
二. dev-server的热替换
1. 简介
PS:
特别是当页面有计数器的情况下,HMR更加适合,因为不需要刷新整个页面,计数器就不会被重置。
2. 如何配置
(1). babel.config.js中的配置
devServer: { hot: true, }
(2). 需要动态指定哪个模块需要HMR
hmr.js代码如下:
console.log('hmr11111122333');
导入main.js中(必须先import导入,再配置)
// 测试HMR import '@js/hmr'; if (module.hot) { module.hot.accept("./js/hmr.js", () => { console.log("hmr模块发生更新了"); }) }
(3). 运行结果
3. HMR的原理
(1). 说明
vue开发中,我们使用vue-loader,此loader支持vue组件的HMR,提供开箱即用的体验;不需要单独配置module.hot.accept
(2). 原理剖析
三. dev-server的其它配置
(更多配置参考官网:https://webpack.js.org/configuration/dev-server/)
1. 基本配置
(1). static→directory:用于设置后打包后寻找静态资源的文件夹
(2). hot:设置为true,开启热替换。
(3). host:设置主机地址,默认值为localhost
(4). port:设置监听的端口,默认情况下是8080
(5). open:是否打开浏览器。 默认值是false,设置为true会打开浏览器。
(6). compress:是否为静态文件开启gzip compression: 默认值是false,可以设置为true;
分享完整节点:
module.exports = { target: "web", devServer: { static: { directory: path.join(__dirname, 'public'), //表示从这个文件夹里找打包后没有找到的文件(打包是打src文件夹) }, hot: true, host: "localhost", port: 9999, open: true, compress: true }, }
补充几个本地地址的区别:
2. 代理配置
(1). 简介
proxy是我们开发中非常常用的一个配置选项,它的目的设置代理来解决跨域访问的问题:
A. 比如我们的一个api请求是 http://localhost:8888,但是本地启动服务器的域名是 http://localhost:8000,这个时候发送网络请求就会出现跨域的问题;
B. 那么我们可以将请求先发送到一个代理服务器,代理服务器和API服务器没有跨域的问题,就可以解决我们的跨域问题了;
PS:在实际开发中,跨域问题通常要配合服务器端一起解决,比如服务器端设置cors等等。
(2). 常用配置
target:表示的是代理到的目标地址,比如 /api-hy/moment会被代理到 http://localhost:8888/api-hy/moment;
pathRewrite:默认情况下,我们的 /api-hy 也会被写入到URL中,如果希望删除,可以使用pathRewrite;
secure:默认情况下不接收转发到https的服务器上,如果希望支持,可以设置为false;
changeOrigin:它表示是否更新代理后请求的headers中host地址;
historyApiFallback是开发中一个非常常见的属性,它主要的作用是解决SPA页面在路由跳转之后,进行页面刷新时,返回404的错误。boolean值:默认是false。如果设置为true,那么在刷新时,返回404错误时,会自动返回 index.html 的内容;object类型的值,可以配置rewrites属性(了解):可以配置from来匹配路径,决定要跳转到哪一个页面;
完整代码:
devServer: { static: { directory: path.join(__dirname, 'public'), //表示从这个文件夹里找打包后没有找到的文件(打包是打src文件夹) }, hot: true, host: "localhost", port: 9999, open: true, compress: true, // 下面是代理相关的配置 proxy: { "/api": { target: "http://localhost:8888", pathRewrite: { "^/api": "" }, secure: false, changeOrigin: true } } },
四. resolve模块解析
1. 背景
在开发中我们会有各种各样的模块依赖,这些模块可能来自于自己编写的代码,也可能来自第三方库;
resolve可以帮助webpack从每个 require/import 语句中,找到需要引入到合适的模块代码;
webpack 使用 enhanced-resolve 来解析文件路径;
2. webpack识别的3中路径
(1). 绝对路径
由于已经获得文件的绝对路径,因此不需要再做进一步解析。
(2). 相对路径
在这种情况下,使用 import 或 require 的资源文件所处的目录,被认为是上下文目录;
在 import/require 中给定的相对路径,会拼接此上下文路径,来生成模块的绝对路径;
(3). 模块路径
在 resolve.modules中指定的所有目录检索模块;
A. 默认值是 ['node_modules'],所以默认会从node_modules中查找文件;
B. 我们可以通过设置别名的方式来替换初识模块路径,后面讲解alias的配置;
3. 区分文件 or 文件夹
(1). 如果是一个文件:
A. 如果文件具有扩展名,则直接打包文件;
B. 否则,将使用 resolve.extensions选项作为文件扩展名解析;(默认情况下,.js/wasm/mjs/json结尾的可以不用写后缀)
(2). 如果是一个文件夹:
会在文件夹中根据 resolve.mainFiles配置选项中指定的文件顺序查找;
A. resolve.mainFiles的默认值是 ['index'];
B. 再根据 resolve.extensions来解析扩展名;
4. 代码配置(重要)
(1). extensions是解析到文件时自动添加扩展名:
默认值是 ['.wasm', '.mjs', '.js', '.json'];所以如果我们代码中想要添加加载 .vue 或者 jsx 或者 ts 等文件时,我们必须自己写上扩展名;
(2). 配置别名alias:
特别是当我们项目的目录结构比较深的时候,或者一个文件的路径可能需要 ../../../这种路径片段;我们可以给某些常见的路径起一个别名;
配置分享
resolve: { extensions: [".js", ".json", ".mjs", ".vue", ".ts", ".jsx", ".tsx"], //vue ts jsx tsx 是自己添加的后缀 alias: { "@": path.resolve(__dirname, "./src"), "@js": path.resolve(__dirname, "./src/js") } },
代码实战改写
// commonjs的导入 // const { getPrice } = require('./js/t1'); // 配置resolve后的写法 const { getPrice } = require('@js/t1'); // ES6的导入 // import { sum } from './js/t2.js'; // 配置resolve后的写法 import { sum } from '@js/t2'; // 导入创建html的js // import './js/createHtml.js'; // 配置resolve后的写法 import '@js/createHtml'; // 导入需要babel转换的js文件 // import './js/ypf1.js'; // 配置resolve后的写法 import '@js/ypf1';// 调用vue相关 import {createApp} from 'vue'; // import App from './vue/App.vue'; // 配置resolve后的写法 import App from '@/vue/App';
五. 区分开发/生产环境实战
1. 实现思路
编写多个的配置文件,开发和生成时,分别加载不同的配置文件即可。
2. 实现步骤
(1). 通过指令安装下面程序,用于合并js文件
【npm install webpack-merge -D】
(2). 在config目录下新建3个js文件:
webpack.common.conf.js (公共的)
// 这里必须通过 commonjs的写法配置,不能写 ES6的写法 const path = require('path'); // 导入插件 const HtmlWebpackPlugin = require("html-webpack-plugin"); const { DefinePlugin } = require("webpack"); const { VueLoaderPlugin } = require('vue-loader/dist/index'); module.exports = { target: "web", entry: "./src/main.js", //入口文件 output: { path: path.resolve(__dirname, "../build"), //打包后存放路径, 必须写绝对路径 filename: "bundle.js" //打包后的文件名称 }, resolve: { extensions: [".js", ".json", ".mjs", ".vue", ".ts", ".jsx", ".tsx"], //vue ts jsx tsx 是自己添加的后缀 alias: { "@": path.resolve(__dirname, "../src"), "@js": path.resolve(__dirname, "../src/js") } }, module: { rules: [{ test: /\.css$/, //(表示所有的css结尾的文件都进行匹配) use: ["style-loader", "css-loader"] }, { test: /\.less$/, //(表示所有的less结尾的文件都进行匹配) use: ["style-loader", "css-loader", "less-loader"] }, // 文件解析功能,"asset/resource"(等价于file-loader ) { test: /\.(jpe?g|png|gif|svg)$/, type: "asset/resource", generator: { filename: "img/[name]_[hash:6][ext]" } }, // 解析字体(也可以用file-loader处理) { test: /\.(eot|ttf|woff2?)$/, type: "asset/resource", generator: { filename: "font/[name]_[hash:6][ext]" } }, // Vue解析打包 { test: /\.vue$/, loader: "vue-loader" } ] }, plugins: [ new HtmlWebpackPlugin({ template: "./public/index.html", title: "hello webpack" }), new DefinePlugin({ BASE_URL: "'./'", __VUE_OPTIONS_API__: true, __VUE_PROD_DEVTOOLS__: false }), new VueLoaderPlugin() ] }
注意:
(1). entry: "./src/main.js", 入口文件的位置,不需要改成 ../src/main.js,因为入口文件其实是和另一个属性时有关的 context,默认应该是webpack的启动目录(当前路径)
(2). 其它的地方需要相应的改变路径,比如:path: path.resolve(__dirname, "../build"),
webpack.dev.conf.js(开发时用)
const path = require('path'); const { merge } = require('webpack-merge'); const commonConfig = require('./webpack.common.conf'); module.exports =merge(commonConfig, { mode: "development", // 设置模式, development 开发阶段, 会设置development, production 准备打包上线的时候, 设置production(默认) devtool: "source-map", // 设置source-map, 建立js映射文件, 方便调试代码和错误(配合上面的development) devServer: { static: { directory: path.join(__dirname, 'public'), //表示从这个文件夹里找打包后没有找到的文件(打包是打src文件夹) }, hot: true, host: "localhost", port: 9999, open: true, compress: true, // 下面是代理相关的配置 // proxy: { // "/api": { // target: "http://localhost:8888", // pathRewrite: { // "^/api": "" // }, // secure: false, // changeOrigin: true // } // } }, })
注意:
需要合并webpack.common.conf.js
webpack.pro.conf.js(生产时用)。
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); const CopyWebpackPlugin = require('copy-webpack-plugin'); const { merge } = require('webpack-merge'); const commonConfig = require('./webpack.common.conf'); module.exports =merge(commonConfig, { mode: "production", // 设置模式, development 开发阶段, 会设置development, production 准备打包上线的时候, 设置production(默认) plugins: [ new CleanWebpackPlugin(), new CopyWebpackPlugin({ patterns: [{ from: "public", //设置从哪一个源中开始复制; // to: "./", //复制到的位置,可以省略,会默认复制到打包的目录下; globOptions: { //下面配置忽略清单 ignore: [ "**/index.html" ] } }] }), ] })
注意:
需要合并webpack.common.conf.js
(3). 在package.json中修改相关指令的调用文件
"scripts": { "build": "webpack --config ./config/webpack.pro.conf.js", "serve": "webpack serve --config ./config/webpack.dev.conf.js" },
(4). 测试
【npm run build】打包
【npm run serve】开发运行
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。