写在最前面:切换npm源。
npm config get registry
# registry.npm.taobao.org 的 HTTPS 证书到期 npm config set registry https://registry.npm.taobao.org # 2024-02-22 更新 # 请使用最新(下方)的 npmjs.com 镜像,官网:https://npmmirror.com/ npm config set registry https://registry.npmmirror.com
这里简单的记录一下:Vue3 + webpack下的多入口demo。该demo并没有使用
这是第一步,仅仅是Vue3 + webpack的组合,只是弄了个多入口。没有使用单文件组件、
先看一下demo目录结构,编译前后做个对比:
上面的对比图,你可以忽略tree.txt文件。你可以使用下面的命令生成目录结构文件:
tree /f >tree.txt
开篇说了处理多个入口页面,所以这里创建了两个文件夹(module1和module2)来模拟没有任何关系的两个子模块。这两个模块的代码没有什么大的不同,只是1和2的区别。
模块1的路径:
模块2的路径:
import * as Vue from "vue"; Vue.createApp({ data() { return { msg: "Hi,这里是 Vue3 + webpack 多入口 示例项目 模块1 入口页面" } } }).mount('#vue3Module1Entry');
{ "name": "vue3-webpack-multi-entry-demo", "version": "1.0.0", "main": "index.js", "license": "MIT", "scripts": { "start": "webpack --progress --watch" } }
# 你可以用下面的命令安装 yarn add vue@next yarn add html-webpack-plugin webpack webpack-cli -D
// yarn add vue@next // yarn add html-webpack-plugin webpack webpack-cli -D const path = require('path'); const webpack = require('webpack'); const HtmlWebPack = require('html-webpack-plugin'); let multiPageConfig = [{ entry: { sourcePath: "./src/module1/js/index.js", // entry.key中以'/'分割,最后一项是文件的名称,前面的都是目录 targetPath: "module1/js/index" }, htmlWebPack: { sourcePath: './src/module1/index.html', targetPath: 'module1/index.html' } }, { entry: { sourcePath: "./src/module2/js/index.js", targetPath: "module2/js/index" }, htmlWebPack: { sourcePath: './src/module2/index.html', targetPath: 'module2/index.html' } }]; let tempModuleExportsObj = { mode: 'development', // production | development devtool: "source-map", entry: {}, output: { filename: '[name].js', path: path.resolve(__dirname, './www'), //publicPath: '/' // 为了可以在github上可以浏览,这里做下面的修改 publicPath: '/vue3/www/' }, module: { rules: [] }, resolve: { alias: { // [Vue warn]: Component provided template option but runtime compilation is not supported in this build of Vue. Configure your bundler to alias "vue" to "vue/dist/vue.esm-bundler.js". 'vue': 'vue/dist/vue.esm-bundler.js' } }, plugins: [ // Feature flags __VUE_OPTIONS_API__, __VUE_PROD_DEVTOOLS__ are not explicitly defined. You are running the esm-bundler build of Vue, which expects these compile-time feature flags to be globally injected via the bundler config in order to get better tree-shaking in the production bundle. new webpack.DefinePlugin({ __VUE_OPTIONS_API__: true, __VUE_PROD_DEVTOOLS__: false, }) ] }; multiPageConfig.forEach(singlePage => { let tempEntryObj = {}; tempEntryObj[singlePage.entry.targetPath] = singlePage.entry.sourcePath; Object.assign(tempModuleExportsObj.entry, tempEntryObj); let tempHtmlWebPackOption = { inject: true, hash: true, //开启hash ?[hash] chunks: [singlePage.entry.targetPath], //页面要引入的包 }; Object.assign(tempHtmlWebPackOption, { template: singlePage.htmlWebPack.sourcePath, filename: singlePage.htmlWebPack.targetPath }); tempModuleExportsObj.plugins.push(new HtmlWebPack(tempHtmlWebPackOption)); }); module.exports = tempModuleExportsObj;
先来张图看看结构的变化
既然是单文件组件,先来一个,尽量简单(路径为:/src/common/component/DisplayText.vue):就是展示一些文本。下面是组件代码:
{{value}}
两个html页面都添加下面的内容
对应的js文件用下面的替换(记得改一下两个页面的区别)
import * as Vue from "vue"; import DisplayText from '../../common/component/DisplayText.vue'; Vue.createApp({ data() { return { msg: "Hi,这里是 Vue3 + webpack 多入口 示例项目 模块1 入口页面", displayText: "再说一遍,这里是 模块1 入口页面", displayTextStyle: { fontWeight: "bold" } } }, components: { DisplayText } }).mount('#vue3Module1Entry');
新增
# 你可以用下面的命令安装 yarn add vue@next yarn add @vue/compiler-sfc html-webpack-plugin vue-loader@next webpack webpack-cli -D
引入
// 第二版:添加的内容 const { VueLoaderPlugin } = require('vue-loader'); let tempModuleExportsObj = { module: { rules: [ // 第二版:添加的内容 { test: /\.vue$/, use: ['vue-loader'] } ] }, plugins: [ // 第二版:添加的内容 new VueLoaderPlugin() ] };
还是先来张图看看结构的变化
两个模块都添加
// 两个LESS文件公共部分 *, *::before, *::after { box-sizing: border-box; } html, body { height: 100%; width: 100%; margin: 0; } // 模块1 入口页面 样式文件 #vue3Module1Entry { height: 100%; width: 100%; padding: 20px; overflow: hidden; color: chocolate; } // 模块2 入口页面 样式文件 #vue3Module2Entry { height: 100%; width: 100%; padding: 10px; overflow: hidden; color: blue; }
两个入口页面对应的JS文件引入LESS(这里放在了JS文件的最上方)
// 两个入口页面对应的JS文件新增引入样式 import '../css/index.less';
新增
# 你可以用下面的命令安装 yarn add vue@next yarn add @vue/compiler-sfc css-loader html-webpack-plugin less less-loader style-loader vue-loader@next webpack webpack-cli -D
rules中添加LESS文件处理
let tempModuleExportsObj = { module: { rules: [ // 第三版:添加的内容 { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] } ] } };
还是先来张图看看结构的变化
1、公共文件夹
2、两个模块下各添加两张图片:一张作为
3、两个模块下都创建一个特有单文件组件(路径为:/src/module1/component/Module.vue),这两个组件的代码相同,该组件展示一张特有的图片;
单文件组件:这里展示一张图片
4、两个入口文件对应的LESS文件以
// 新增加的内容 body { background: url(../images/bg.png) repeat-x; }
此次添加的图片你可以在这里找到: 快去找图片吧
// 引入组件 import Module from '../component/Module.vue'; // 注册组件 components: { Module }
新增
yarn add vue@next yarn add @vue/compiler-sfc css-loader html-loader html-webpack-plugin less less-loader style-loader vue-loader@next webpack webpack-cli -D
rules中添加HTML文件处理以及静态文件(这里是图片)处理
let tempModuleExportsObj = { module: { rules: [ // 第四版:添加的内容 { test: /\.html$/, use: ['html-loader'] }, { test: /\.(jpg|jpeg|png|gif|svg|eot|svg|ttf|woff|woff2)$/, type: 'asset/resource', generator: { filename(a, b) { // a.asdf => "src/common/images/logo.png" return path.relative(path.join(__dirname, "src"), path.resolve(a.filename)).split(path.sep).join("/"); } } } ] } };
记得上次写的时候是通过
转ES5在实际项目中用的太多了,之前在vue2.x的时候也弄了,但是此时按照原来的方式就行不通了,出现了一些问题,最后发现竟然是
原来的vue相关的代码没有变动,只是增加了一个新的模块(module3)来测试ES5的转换(路径为:/src/module3/index.html 和 /src/module3/js/index.js)。
document.addEventListener("DOMContentLoaded", function () { document.getElementById("array-from-1").innerHTML = Array.from("利用Array.from将字符串转成数组!"); document.getElementById("array-from-2").innerHTML = JSON.stringify(Array.from({ 0: null, 1: undefined, 2: true, 3: 666, 4: "我是字符串", 5: Symbol("key1"), 6: { key1: 1, key2: 2 }, 7: ["a", "b", "c"], 8: function () {}, length: 9, })); document.getElementById("array-from-3").innerHTML = Array.from({ 1: "b", 3: "d", length: 6 }); Promise.resolve("Promise的返回结果:888").then((ret) => { document.getElementById("promise-1").innerHTML = ret; }); });
在处理ES6+转ES5的时候,当时就查到了两种处理方式,添加的依赖项不同,这里都记录一下:
# 这里是没有转码的命令(也就是上一版本的),你可以先试试他,之后再试试下面的两种方式,做一下对比 yarn add vue@next yarn add @vue/compiler-sfc css-loader html-loader html-webpack-plugin less less-loader style-loader vue-loader@next webpack webpack-cli -D
# 你可以用下面的命令安装 yarn add vue@next # 方式一添加的依赖:@babel/core @babel/preset-env babel-loader core-js regenerator-runtime yarn add @babel/core @babel/preset-env @vue/compiler-sfc babel-loader core-js css-loader html-loader html-webpack-plugin less less-loader regenerator-runtime style-loader vue-loader@next webpack webpack-cli -D
# 你可以用下面的命令安装 yarn add vue@next # 方式二添加的依赖:@babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/runtime @babel/runtime-corejs3 babel-loader yarn add @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/runtime @babel/runtime-corejs3 @vue/compiler-sfc babel-loader css-loader html-loader html-webpack-plugin less less-loader style-loader vue-loader@next webpack webpack-cli -D
// yarn add vue@next // ES6+转ES5,第一种实现方式新增的依赖:@babel/core @babel/preset-env babel-loader core-js regenerator-runtime // yarn add @babel/core @babel/preset-env @vue/compiler-sfc babel-loader core-js css-loader html-loader html-webpack-plugin less less-loader regenerator-runtime style-loader vue-loader@next webpack webpack-cli -D // ES6+转ES5,第二种实现方式新增的依赖:@babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/runtime @babel/runtime-corejs3 babel-loader // yarn add @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/runtime @babel/runtime-corejs3 @vue/compiler-sfc babel-loader css-loader html-loader html-webpack-plugin less less-loader style-loader vue-loader@next webpack webpack-cli -D let multiPageConfig = [ // 第五版:添加的内容 { entry: { sourcePath: "./src/module3/js/index.js", targetPath: "module3/js/index" }, htmlWebPack: { sourcePath: './src/module3/index.html', targetPath: 'module3/index.html' } } ]; let tempModuleExportsObj = { // 第五版:添加的内容 target: ['web', 'es5'], module: { rules: [ // 第五版:添加html-loader配置项和添加babel-loader { test: /\.html$/, loader: 'html-loader', options: { sources: { // // 第一版使用的方式,但是不太好,不能过滤标签 // urlFilter: (attrName, value, absolutePath) => { // // attrName => "src" // // value => "/p/_/js/main.js" // // absolutePath => "F:\……\src\module3\index.html" // if (attrName === "src" && value.startsWith("/")) { // return false; // } // return true; // }, list: [ "...", { tag: "script", attribute: "src", type: "src", filter: (tagName, attrName, kvs, absolutePath) => { // tagName => "script" // attrName => "src" // kvs => [{"name":"src","value":"/p/_/js/main.js"}] // absolutePath => "F:\……\src\module3\index.html" let srcKV = null; if (tagName === "script" && attrName === "src" && Array.isArray(kvs) && (srcKV = kvs.filter(x => x.name === attrName)[0])) { if (srcKV.value.startsWith("/")) { return false; } } return true; } }, ] } } }, { test: /\.m?js$/, exclude: /(node_modules|bower_components)/, use: { loader: "babel-loader", options: { /** * 第一种方式 * 如果独立使用,你可以使用下面的命令(其他的项目,单独转码使用) * yarn add @babel/core @babel/preset-env babel-loader core-js html-loader html-webpack-plugin regenerator-runtime webpack webpack-cli -D */ presets: [ ["@babel/preset-env", { modules: false, useBuiltIns: "usage", corejs: { version: 3, proposals: true, }, targets: { ie: 8 } }] ], /** * 第二种方式 * 如果独立使用,你可以使用下面的命令(其他的项目,单独转码使用) * yarn add @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/runtime @babel/runtime-corejs3 babel-loader html-loader html-webpack-plugin webpack webpack-cli -D */ // presets: [ // ["@babel/preset-env"] // ], // plugins: [ // ['@babel/plugin-transform-runtime', { // corejs: { // version: 3, // proposals: true // }, // useESModules: true // }] // ] } } } ] }, };
html-loader:这里做了一些配置,目的是排除一些js(站点根目录开始的)
图片中的代码对比:一些注释代码给忽略了,看详细的你可以下载代码。
最后再来看一下转ES5前后的截图对比
默认打包时将依赖和业务代码都弄到一个JS中了,看着不舒服,希望打包之后的代码也有一个清晰的结构,仅仅是为了看着舒服……看一下目录变化
新增公共方法类库(路径为:/src/common/js/utils.js)
/*! utils.js 业务无关通用方法 */ /** * 获取一个GUID * 例子:getGUID();=>'AEFC9ABC-1396-494B-AB96-C35CA3C9F92F' * @returns {string} 返回一个GUID */ export const getGUID = function () { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16).toUpperCase(); }); }
一个GUID:{{guid1}}
// 第六版新增 import * as utils from '../../common/js/utils'; // 这里放在vue的data中 guid1: utils.getGUID()
// 第六版新增 import * as utils from '../../common/js/utils'; // 下面的代码放到DOMContentLoaded监听回调中 document.getElementById("guid1").innerHTML = "一个GUID:" + utils.getGUID();
这次并没有添加新的依赖,上图中的变化是版本的升级。
webpack.config.js添加下面的配置型
{ optimization: { // 优化项 splitChunks: { //分割代码块 cacheGroups: { // 缓存 'my': { // 自己整理的通用方法 priority: 100, name: "common/js/common", // 以'/'分割,最后一项是文件的名称,前面的都是目录 test: /[\\/]src[\\/](common)[\\/]/, chunks: 'initial', minSize: 0, minChunks: 1, filename: '[name].js' }, 'core-js': { // 抽取第三方模块:core-js priority: 10, name: 'core-js', test: /[\\/]node_modules[\\/](core-js)[\\/]/, chunks: 'initial', minSize: 0, minChunks: 1, filename: 'lib/[name]/[name].js' }, 'vue': { // 抽取第三方模块:vue priority: 9, name: 'vue', test: /[\\/]node_modules[\\/](@vue|vue|vue-loader)[\\/]/, chunks: 'initial', minSize: 0, minChunks: 1, filename: 'lib/[name]/[name].js' }, 'other': { // 公共的代码 priority: 8, name: "other", chunks: 'initial', minSize: 0, minChunks: 2, // 这个代码引用多少次才需要抽离 filename: 'lib/[name]/[name].js' } } } } }
前面的示例中用到的css都弄到了JS中,感觉不是很爽,希望webpack源码项目和打包之后的目录尽量保持一致,这也是这片文档的宗旨……
这次提取CSS,代码目录没有改变……
新增
# 你可以用下面的命令安装。上一版本没有添加新的依赖,这里使用ES6转ES5的方式1,在这基础上添加 mini-css-extract-plugin yarn add vue@next yarn add @babel/core @babel/preset-env @vue/compiler-sfc babel-loader core-js css-loader html-loader html-webpack-plugin less less-loader mini-css-extract-plugin regenerator-runtime style-loader vue-loader@next webpack webpack-cli -D
引入
// 第七版:添加的内容 const MiniCssExtractPlugin = require("mini-css-extract-plugin"); let tempModuleExportsObj = { module: { rules: [ // 第七版:更新 { test: /\.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'] } ] }, plugins: [ // 第七版:添加的内容 new MiniCssExtractPlugin({ filename: ({ chunk }) => `${chunk.name.replace('/js/', '/css/')}.css`, }) ] };
最后再看一下,不提取CSS和提取之后的目录对比:
这里还发现了一个问题,html页面对js的引入都添加到了head标签下并且提取css文件都放在script标签的后面,赶紧处理一下吧
查了一下,发现是html-webpack-plugin插件
这里在添加两个插件:clean-webpack-plugin 生成之前先删除(你手动添加的文件也会删除);copy-webpack-plugin 拷贝文件到指定的目录。这两个插件本文档使用的比较简单,就不添加代码下载了。
创建一个文件(路径为:/src/mock/test.json),文件随便写点,你随意
# 你可以用下面的命令安装 yarn add clean-webpack-plugin copy-webpack-plugin -D
// 第八版:添加的内容 const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); let tempModuleExportsObj = { plugins: [ // 第八版:添加的内容 new CleanWebpackPlugin(), new CopyWebpackPlugin({ patterns: [{ from: 'src/mock', to: 'mock' }, ] }) ] };