vue2+微前端qiankun从搭建到部署的实践(主子应用切换;集成vue3+vite3子应用)
一、最终效果
二、微前端(qiankun)介绍及为什么选择用微前端,可以看官网
三、目录结构如下
四、具体配置
一、主应用配置
1、主应用技术栈
Vue-cli4搭建项目+Vue2+Element-Ui+qiankun;Vue2+Element-Ui+qiankun
2、搭建好主项目(即:常规的vue2后台管理系统),后续我会开源一套vue2后台管理系统模板
3、在主项目目录src下新建一个micro-app.js文件(main/src/micro-app.js)
整合所有子应用,为了方便管理(后续可以在系统管理中开个微前端配置页面——调用接口来获取)
const microApps = [{name: 'portal-fe',// entry: process.env.VUE_APP_SUB_VUE2,entry: '//localhost:9100/wocwin-qiankun/app-vue2/',activeRule: '/wocwin-qiankun/app-vue2'},{name: 'vue3-vite-fe',// entry: process.env.VUE_APP_SUB_VUE3,entry: '//localhost:3300/app-vue3vite/',activeRule: '/app-vue3vite'}
]
const apps = microApps.map(item => {return {...item,container: '#app', // 子应用挂载的divprops: {routerBase: item.activeRule // 下发基础路由}}
})
export default apps
4、在main.js中注册子应用(main/src/main.js)
import { registerMicroApps, start } from 'qiankun'
// 获取所有子应用
import microApps from './micro-app'// 给子应用配置加上loader方法
const apps = microApps.map(item => {// console.log('app', item)return {...item}
})
registerMicroApps(apps, {beforeLoad: (app) => {console.log('before load', app)switch (app.name) {case 'portal-fe':document.title = 'vue2常规模板'breakcase 'vue3-vite-fe':document.title = 'vue3+vite模板'break}},beforeMount: [(app) => {console.log('before mount', app.name)}]
})
start({prefetch: false // 取消预加载
})
5、具体跳转子应用需要用到window.history.pushState()
注意:调用这个方法前提:路由是 history 模式
window.history.pushState(state, title, url)
// 1、state:一个与添加的记录相关联的状态对象,主要用于popstate事件。该事件触发时,该对象会传入回调函数。也就是说,浏览器会将这个对象序列化以后保留在本地,重新载入这个页面的时候,可以拿到这个对象。如果不需要这个对象,此处可以填null。
///2、title:新页面的标题。但是,现在所有浏览器都忽视这个参数,所以这里可以填空字符串。
// 3、url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。
二、vue2子应用配置
1、vue2子应用技术栈跟主项目一样
2、具体需要修改以下几个部分(跟主应用的micro-app.js一致)
1、子应用的package.json ,name要与microApps 的name一样
2、如下修改src/main.js文件
let instance = null
export function render(props = {}) {console.log('执行子应用渲染')if (window.__POWERED_BY_QIANKUN__) {// eslint-disable-next-line no-undef__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__}const { container } = propsinstance = new Vue({router,store,render: (h) => h(App)}).$mount(container ? container.querySelector('#app') : '#app')console.log('开始加载相关内容')
}
// TODO:非qiankun环境下单独运行
// @ts-ignore
if (!window.__POWERED_BY_QIANKUN__) {console.log('并不是qiankun渲染')render()
}
/*** bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。*/
export async function bootstrap() {console.log('react app bootstraped')
}/*** 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法*/
export async function mount(props) {console.log('应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法', props)props.onGlobalStateChange((state) => {console.log('子应用接收的参数', state)if (state.menuId) {store.commit('SET_MENU_ID', state.menuId)}}, true)render(props)
}
/*** 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例*/
export async function unmount(props) {if (instance) {instance.$destroy()instance = null}await store.dispatch('tagsView/delAllViews')console.log('应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例', props)
}
/*** 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效*/
export async function update(props) {console.log('update props', props)
}
3、vue.config.js文件新增以下代码
const packageName = require('./package.json').name
module.exports = {publicPath: '/wocwin-qiankun/app-vue2', // 这个要与主应用的micro-app.js中activeRule一致
// 解决本地启动主子切换报跨域问题
devServer: {headers: {'Access-Control-Allow-Origin': '*'}},configureWebpack: {// 把子应用打包成 umd 库格式output: {library: `${packageName}`,libraryTarget: 'umd', // 把微应用打包成 umd 库格式jsonpFunction: `webpackJsonp_${packageName}`,filename: `[name].[hash].js`,chunkFilename: `[name].[hash].js`}}
}
4、如下修改src/router/index.js文件
import Vue from 'vue'
import Router from 'vue-router'Vue.use(Router)/* Layout */
import Layout from '@/layout'
import login from '@/views/login.vue'
import redirect from '@/views/redirect.vue'// 公共路由
export const constantRoutes = window.__POWERED_BY_QIANKUN__? [{path: '/login',name: 'login',component: login,hidden: true,meta: {rootPage: true,noCache: true}},{path: '/redirect',name: 'redirect',component: Layout,hidden: true,children: [{path: ':path(.*)',name: 'redirectPage',component: redirect,meta: {noCache: true}}]},{path: '/',component: Layout,redirect: '/base',// hidden: true,children: [{path: 'base',component: () => import('../views/demo/TTable/base.vue'),name: 'base',meta: { title: 'vue2常规模板', icon: 'monitor' }}]}]: [{path: '/redirect',component: Layout,hidden: true,children: [{path: '/redirect/:path(.*)',component: () =>import('@/views/redirect')}]},{path: '/login',component: () =>import('@/views/login'),hidden: true},{path: '/404',component: () =>import('@/views/error/404'),hidden: true},{path: '/401',component: () =>import('@/views/error/401'),hidden: true},{path: '/',component: Layout,redirect: '/base',children: [{path: 'base',component: () => import('../views/demo/TTable/base.vue'),name: 'base',meta: { title: 'vue2常规模板', icon: 'monitor' }}]}]
const router = new Router({base: '/wocwin-qiankun/app-vue2/',routes: constantRoutes,mode: 'history'
})
export default router
5、如下修改部分登出文件(判断是回主应用登录还是子应用登录页面)
二、vue3+vite3子应用配置
1、创建Vue3+Vite项目
可以参考我之前发布的vite快速搭建vue3项目文章来创建;也可以直接使用我开源Vue3.2+Ts+Vite3+Pinia+Element-Plus模板wocwin-admin
以下我以 wocwin-admin 项目为例
具体步骤可以参考这篇文章
2、抽离贴出重点代码
1、vue3Vite/src/main.ts文件修改
/*** 配置qiankun*/
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
let instance: any = null
function render(props: any = {}) {const { container } = propsinstance = createApp(App)instance.use(router)instance.use(pinia)// 注册全局api方法instance.config.globalProperties.$api = api// 注册所有图标for (const [key, component] of Object.entries(ElementPlusIconsVue)) {instance.component(key, component)}// 注册ElementPlusinstance.use(ElementPlus, {locale // 语言设置// size: Cookies.get('size') || 'medium' // 尺寸设置})instance.use(TuiPlus)// 全局组件祖册instance.component('SvgIcon',// 如果这个组件选项是通过 `export default` 导出的,那么就会优先使用 `.default`,否则回退到使用模块的根SvgIcon.default || SvgIcon)instance?.mount(container ? container.querySelector('#app') : '#app')console.log('开始加载相关内容')
}
renderWithQiankun({mount(props: any) {render(props)},bootstrap() {console.log('%c', 'color:green;', ' ChildOne bootstrap')},update() {console.log('%c', 'color:green;', ' ChildOne update')},unmount(props: any) {console.log('unmount', props)instance.unmount()instance._container.innerHTML = ''instance = null}
})if (!qiankunWindow.__POWERED_BY_QIANKUN__) {console.log('并不是qiankun渲染')render()
}
2、vue3Vite/package.json文件修改
name要与主应用的microApps 的name一样
3、vue3Vite/vite.config.ts文件修改
// 配置qiankun
import qiankun from 'vite-plugin-qiankun'
const packName = require('./package').name
export default defineConfig({base: '/app-vue3vite/', // 这个要与主应用的micro-app.js中activeRule一致 plugins: [...// 配置qiankunqiankun(`${packName}`, {useDevMode: true})],server: {headers: {'Access-Control-Allow-Origin': '*'}},
})
4、vue3Vite/src/router/index.ts文件修改
import { createWebHistory } from 'vue-router'
const router = createRouter({history: createWebHistory('/app-vue3vite/'), // 这个要与主应用的micro-app.js中activeRule一致 routes: constantRoutes,
})
export default router
三、若想实现主子应用之间通信可以参考这一篇博客
四、本地部署到服务器需要配置NG(可以参考下面代码)
这种配置的好处:所有子应用都是挂载在主应用中,子应用无需在新开一个端口,若服务器部署成功,单独子应用项目地址:主应用地址+子应用的publicPath
################################################
#### 门户 PC前端主-子项目NGINX统一代理规则 ####
################################################# nginx配置
server {listen 8100;server_name localhost;gzip on;gzip_buffers 32 4K;gzip_comp_level 6;gzip_min_length 100;gzip_types application/javascript text/css text/xml;gzip_disable "MISE [1-6]\.";gzip_vary on;# pc端主应用location / {add_header Access-Control-Allow-Origin *;add_header Cache-Control no-cache;# root 根目录,默认nginx镜像的html文件夹,可以指定其他root /data/build/nodejs/wocwin-qiankun-main/dist-wocwin-qiankun-main;index index.html index.htm;# 如果vue-router使用的是history模式,需要设置这个try_files $uri $uri/ /index.html;if ($request_filename ~* ^.*?\.(doc|pdf|docx)$) {add_header Content-Disposition "attachment";add_header Content-Type application/octet-stream;}}#### vue2常规模板location /app-vue2 {add_header Access-Control-Allow-Origin *;add_header Cache-Control no-cache;alias /data/build/nodejs/portal-fe-dev/dist-portal-fe/;index index.html index.htm;try_files $uri $uri/ /app-vue2/index.html;if ($request_filename ~* ^.*?\.(doc|pdf|docx)$) {add_header Content-Disposition "attachment";add_header Content-Type application/octet-stream;}}## vue3模板location /app-vue3vite {add_header Access-Control-Allow-Origin *;add_header Cache-Control no-cache;# root 根目录,默认nginx镜像的html文件夹,可以指定其他alias /data/build/nodejs/vue3-vite-fe-dev/dist-vue3-vite-fe/;index index.html /index.htm;# 如果vue-router使用的是history模式,需要设置这个try_files $uri $uri/ /app-vue3vite/index.html;if ($request_filename ~* ^.*?\.(doc|pdf|docx)$) {add_header Content-Disposition "attachment";add_header Content-Type application/octet-stream;}}######################## 转发后端接口location ^~ /sso/ {proxy_pass http://*********/sso/; # 统一登录}location ^~ /user/ {proxy_pass http://*********/user/; # 统一用户}# 单个子应用业务后台接口地址location ^~ /mes/ {proxy_pass http://******/mes/;}################################## WS转发配置################################ 单个子应用websocket地址location ^~ /***/ws/ {proxy_pass http://****/ws/;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "Upgrade";proxy_set_header X-Real-IP $remote_addr;}#error_page 404 /404.html;# redirect server error pages to the static page /50x.html#error_page 500 502 503 504 /50x.html;location = /50x.html {root /usr/local/nginx/html;}
}
五、源码地址
gitHub组件地址
gitee码云组件地址
相关文章
基于ElementUi&Antd再次封装基础组件文档
vue3+ts基于Element-plus再次封装基础组件文档
vite+vue3+ts项目搭建之集成qiankun
相关文章:
vue2+微前端qiankun从搭建到部署的实践(主子应用切换;集成vue3+vite3子应用)
一、最终效果 二、微前端(qiankun)介绍及为什么选择用微前端,可以看官网 三、目录结构如下 四、具体配置 一、主应用配置 1、主应用技术栈 Vue-cli4搭建项目Vue2Element-Uiqiankun;Vue2Element-Uiqiankun 2、搭建好主项目&…...
怎么代理微信小程序创业?
随着微信的兴起,小程序已经成为了人们生活中不可或缺的一部分。如果你想要创业的话,那么代理微信小程序是一个不错的选择。本文将为大家介绍怎么代理微信小程序创业。 一、什么是微信小程序 微信小程序是一款专为移动设备使用者而设计的应用。它通过扫…...
今天是情人节呐,我利用Python制作了好多表白的东西,快来吧~
今天是情人节那,有没有现在没有对象的宝子,评论里扣个111哈哈 目录 玫瑰 爱心树 丘比特 多彩气球 阿玥的小课堂 一、情人节的由来 二、情人节的来历和意义 玫瑰 局部代码实现如下: # 花瓣1 turtle.left(150) turtle.circle(-90, 70) …...
【Linux】-- 进程信号(处理、内核)
上篇:【Linux】-- 进程信号(认识、应用)_川入的博客-CSDN博客 目录 信号其他相关常见概念 pending handler block 信号处理的过程 sigset_t sigset_t使用 系统接口 sigpending sigprocmask 捕捉方法 sigaction struct sigactio …...
C/【静态通讯录】
🌱博客主页:大寄一场. 🌱系列专栏:C语言学习笔记 😘博客制作不易欢迎各位👍点赞⭐收藏➕关注 前言 往期回顾: C/扫雷 C/N子棋 通讯录作为通讯录地址的书本,当今的通讯录可以涵盖多项…...
万卷书 - 让孩子对自己负责 [The Self-Driven Child]
让孩子对自己负责 The Self-Driven Child - 让你的孩子更加科学合理的掌控自己的生活 简介 《The Self-Driven Child》(2018)解释了我们对孩子的习惯性控制欲,它导致了孩子压力过大、难以合作,以及主观能动性差。本书不提倡这种做法,而是认为我们应该帮助孩子自己做出合适…...
Postman中cookie的操作
在接口测试中,某些接口的调用,需要带入已有Cookie,比如有些接口需要登陆后才能访问。 Postman接口请求使用Cookie有如下两种方式: 1、直接在头域中添加Cookie头域,适用于已经知道请求所用Cookie数据的情况。 2、使用…...
torch.grid_sample
参考: 双线性插值的理论Pytorch grid_sample解析PyTorch中grid_sample的使用方法pytorch中的grid_sample()使用 查阅官方文档,TORCH.NN.FUNCTIONAL.GRID_SAMPLE grid_sample的函数签名如下所示,torch.nn.functional.grid_sample(input, gr…...
前端基于 Docker 的 SSR 持续开发集成环境实践
项目收益 整体开发效率提升20%。加快首屏渲染速度,减少白屏时间,弱网环境下页面打开速度提升40%。 权衡 在选择使用SSR之前,需要考虑以下事项! SSR需要可以运行Node.js的服务器,学习成本相对较高。对于服务器而言&a…...
ARM交叉编译入门及交叉编译第三方库常见问题解析
1. 交叉编译是什么? 交叉编译简单说来,就是编译成果物的地儿不是你运行这个成果物的地儿。最常见的场景,就是我们要编译一个 ARM版本 的可执行程序,但我们编译这个 ARM版本 可执行程序的地方,是在一个 x86_x64 的平台…...
Ruby Web Service 应用 - SOAP4R
什么是 SOAP? 简单对象访问协议(SOAP,全写为Simple Object Access Protocol)是交换数据的一种协议规范。 SOAP 是一种简单的基于 XML 的协议,它使应用程序通过 HTTP 来交换信息。 简单对象访问协议是交换数据的一种协议规范,是一种轻量的、…...
HashMap底层实现原理概述
原文https://blog.csdn.net/fedorafrog/article/details/115478407 hashMap结构 常见问题 在理解了HashMap的整体架构的基础上,我们可以试着回答一下下面的几个问题,如果对其中的某几个问题还有疑惑,那就说明我们还需要深入代码,…...
Linux驱动学习环境搭建
背景常识 一、程序分类 程序按其运行环境分为: 1. 裸机程序:直接运行在对应硬件上的程序 2. 应用程序:只能运行在对应操作系统上的程序 二、计算机系统的层次结构 所有智能设备其实都是计算机,机顶盒、路由器、冰箱、洗衣机、汽…...
Java基础之异常
目录1 异常1.1 异常的概述1.2 常见异常类型1.3 JVM的默认处理方案1.4 编译时异常的处理方式1.4.1 异常处理之 try ... catch ... [ktʃ](捕获异常)1.4.2 异常处理之 throws(抛出异常)1.5 Throwable 的成员方法1.6 编译时异常和运行…...
感慨:大三了,未来该何去何从呢
笔者曾在十一月份通过了字节跳动的三次面试, 但是最终因为疫情原因不能满足公司的入职时间要求, 没有拿到offer。近期也是投递了大量大厂的实习岗, 但是要么已读不回, 要么明确告诉我学历至少要985硕士(天天被阿里cpu)。 说实话一…...
分账系统逻辑
一、说明 主体与业务关系方进行相关利益和支出的分配过程 使用场景: 在分销业务中,主营商户收到用户购买分销商品所支付的款项后,可以通过分账逻辑,与分销商进行佣金结算。在零售、餐饮等行业中,当销售人员完零售等…...
SpringCloud篇——什么是SpringCloud、有什么优缺点、学习顺序是什么
文章目录一、首先看官方解释二、Spring Cloud 的项目的位置三、Spring Cloud的子项目四、Spring Cloud 现状五、spring cloud 优缺点六、Spring Cloud 和 Dubbo 对比七、Spring Cloud 学习路线一、首先看官方解释 Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式…...
TCP核心机制之连接管理详解(三次握手,四次挥手)
目录 前言: 建立连接 建立连接主要两个TCP状态: 断开连接 断开连接的两个重要状态 小结: 前言: TCP是如何建立对端连接,如何断开连接,这篇文章会详细介绍。 建立连接 首先明确连接的概念:…...
前端—环境配置
前端开发建议用 Google Chrome 浏览器 vscode https://code.visualstudio.com 1、open in browser 插件:可以在 vscode 中直接运行查看浏览器效果 2、Live Server 插件:可以使代码修改浏览器页面实时刷新。 用户代码片段 … JavaScript 与 TypeScri…...
大学生常用python变量和简单的数据类型、可迭代对象、for循环的3用法
文章目录变量和简单的数据类型下划线开头的对象删除内存中的对象列表与元组debug三酷猫钓鱼记录实际POS机小条打印使用循环找乌龟可迭代对象📗理解一📘理解二2️⃣什么是迭代器✔️注意3️⃣迭代器对象4️⃣有关迭代的函数for循环的3用法🌸I …...
Java集合:Map的使用
1.Map框架l----Map:双列数据,存储key-value对的数据 ---类似于高中的函数: y f(x)|----HashMap:作为Map的主要实现类, 线程不安全的,效率高;可以存储null的key和value|----LinkedHashMap:保证在遍历map元素时,可以按照…...
【Datawhale图机器学习】第一章图机器学习导论
图机器学习导论 学习路径与必读论文清单 斯坦福CS224W(子豪兄中文精讲)知识图谱实战DeepwalkNode2vecPageRankGNNGCNGragh-SAGEGINGATTrans-ETrans-R 图无处不在 图是描述关联数据的通用语言 举例 计算机网络新冠肺炎流行病学调查传播链食物链地铁图…...
window 配置深度学习环境GPU
CUDA 11.6 CUDNN Anaconda pytorch 参考网址:https://zhuanlan.zhihu.com/p/460806048 阿里巴巴开源镜像站-OPSX镜像站-阿里云开发者社区 (aliyun.com) 电脑信息 RTX 2060 GPU0 1. CUDA 11.6 1.1 确认信息 C:\Users\thzn>nvidia-smi (CUDA Versi…...
VS Code 用作嵌入式开发编辑器
使用 Keil MDK 进行嵌入式开发时,Keil 的编辑器相对于主流编辑器而言有些不方便,比如缺少暗色主题、缺少智能悬停感知(鼠标停在一个宏上,能自动展开最终的宏结果)、代码补全不好用等等,所以推荐使用 VS Cod…...
【Python】网络爬虫经验之谈
爬虫经验之谈对爬虫的认识网站分析技术选型JS逆向反爬机制结语近段时间,因为工作需要做一些爬虫的开发,分享一下走过的坑和实战的经验吧!对爬虫的认识 F12查看的网络请求,找到相应的接口查看一下json数据来源和构造。我爬取的网站…...
数学建模美赛【LaTeX】公式、表格、图片
数学建模美赛【LaTeX】公式、表格、图片 1 宏包 \package{ } 就是在调用宏包,对计算机实在外行的同学姑且可以理解为工具箱。 每一个宏包里都定义了一些专门的命令,通过这些命令可以实现对于一类对象(如数学公式等)的统一排版&a…...
【大数据】YARN节点标签Node Label特性
简介 YARN 的 Node-label 特性能够将不同的机器类型进行分组调度,也可以根据不同的资源要求进行分区调度。运维人员可以根据节点的特性将其分为不同的分区来满足业务多维度的使用需求。YARN的Node-label功能将很好的试用于异构集群中,可以更好地管理和调…...
C# SolidWorks二次开发 API-命令标签页的切换与按钮错乱问题
这是一个网友咨询的问题,说他想控制默认打开文件之后solidworks上方工具栏的当前激活标签页。 之前我们提到过,制作Solidworks的插件也会在上面增加一个标签页,用来放自己开发的命令,经常开发的人肯定会遇到有时候更新版本,或者标…...
ElasticSearch 7.6.1
疑问 ES为什么这么快? 全文检索 听过一个程序扫描文本的每一个单词,针对单词建立索引,并保存该单词在文本中的位置,以及出现的次数。在检索查询时候,通过建立好的索引进行查询,将索引中单词对应的文本位…...
Linux系列 操作系统安装及服务控制(笔记)
作者简介:一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页 目录 前言 一.操作系统 1.Linux系统三大类 (1)ubu…...
风景旅游网页设计/百度关键词seo公司
文章目录1、使用1G内存够,从 10 GB 大的文件中获取出现次数为 top3 多的 IP?2、分析2.1、可能出现的IPV4个数及所需存储空间2.2、分而治之的思路2.2.1、大文件拆小文件(相同IP放到同一组文件)2.2.2、对小文件的 IP 进行遍历计数(I…...
免费下载高清图片素材的网站/关于普通话的手抄报
本文框架如下 第一部分,主要是在阅读代码过程中的日志和笔记; 第二部分,主要介绍了 Redis 的主要框架,以及 Redis 是如何提供服务的,从一个最简单的命令开始讲起; 第三部分,主要介绍 Redis 底…...
安远做网站/seo综合查询怎么关闭
在 OS X El Capitan 中有一个跟安全相关的模式叫 SIP(System Integrity Protection ) ,它禁止让软件以 root 身份来在 mac 上运行,并且对于目录 /System 、/sbin、/usr(不包含/usr/local/) 仅仅供系统使用&…...
政府网站建设运行情况/百度有刷排名软件
1 需求 统计最受欢迎的课程TopN访问次数按地市统计最受欢迎的TopN课程按流量统计最受欢迎的TopN课程 2 在MySQL中创建数据库、创建表 // 创建数据库 create database sparkSql_project;// 创建表 create table day_vedio_access_topn_stat(day varchar(8) not null,class_id…...
macbook air做网站/2023年6月份疫情严重吗
PagerAdapter的方法getCount() 获取viewpager中有多少view instantiateItem() 1.将给定的view添加到viewGroup(容器)中,创建并显示出来 2.返回一个代表新增页面的object(key),通常都是直接返回view本身就可以了&…...
广西住房和城乡建设厅官方网站/seo推广主要做什么的
作者:滴滴公共前端团队 - 水乙 我们在使用 webpack 打包我们的工程模块时,经常会需要 devtool 开启 sourceMap 让我们可以调试代码,但是 webpack 文档中关于 devtool 给出了7种模式,文档也写得非常简略,初学者很难上手…...