vue 快速入门 系列 —— vue loader 下
其他章节请看:
vue loader 下
CSS Modules
CSS Modules 是一个流行的,用于模块化和组合 CSS 的系统。vue-loader 提供了与 CSS Modules 的一流集成,可以作为模拟 scoped CSS 的替代方案。
Tip:请看下面的用法来了解 css modules。
用法
将 App.vue 内容修改为:
<template>
<div>
<p :class="$style.red">
This should be red
</p>
<p :class="{ [$style.red]: apple.isRed }">
Am I red?
</p>
<p :class="[$style.red, $style.bold]">
Red and bold
</p>
</div>
</template>
<script>
export default {
created () {
// -> 类似"red_2hCxILSe"
console.log(`red=${this.$style.red}`)
},
data () {
return {
msg: 'Hello world!',
// 注释掉 apple 也不会报错
apple:{
isRed: false
},
}
},
}
</script>
<style module>
.red {
color: red;
font-size: 2em;
}
.bold {
font-weight: bold;
}
</style>
这段代码,在 <style>
上添加 module 特性。这个 module 特性指引 Vue Loader 作为名为 $style 的计算属性,向组件注入 CSS Modules 局部对象。然后就可以在模板中通过一个动态类绑定来使用它了,就像 $style.red,还可以通过 javascript 访问到它。
接着修改配置来开启 modules:
// webpack.config.js -> module.rules
{
test: /\.css$/i,
use: [
"style-loader",
{
loader: 'css-loader',
options: {
// 开启 CSS Modules
modules: {
// 自定义生成的类名
localIdentName: '[local]_[hash:base64:8]'
}
}
}
]
},
重启服务,页面显示三句文案:
// 红色
This should be red
Am I red?
// 红色 + 粗
Red and bold
控制台输出red=red_2hCxILSe
。
通过浏览器查看生成的代码:
<style>
.red_2hCxILSe {
color: red;
font-size: 2em;
}
.bold_2rUIHzbD {
font-weight: bold;
}
</style>
<div>
<p class="red_2hCxILSe">
This should be red
</p>
<p class="">
Am I red?
</p>
<p class="red_2hCxILSe bold_2rUIHzbD">
Red and bold
</p>
</div>
可选用法
如果你只想在某些 Vue 组件中使用 CSS Modules,你可以使用 oneOf 规则并在 resourceQuery 字符串中检查 module 字符串。
什么意思?从 oneOf 这个关键字我嗅到上面的用法是否只能匹配一种情况。于是给 App.vue 在增加一个 style 的样式:
<template>
<!-- 引用定义的样式 -->
<div class="f-border">
...
</div>
</template>
<style module>
...
</style>
<style>
.f-border{border: 1px solid}
</style>
页面看不到边框(border)效果,普通的 <style>
没有生效。
于是根据文档配置如下:
// webpack.config.js -> module.rules
{
test: /\.css$/,
oneOf: [
// 这里匹配 `<style module>`
{
resourceQuery: /module/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
// 开启 CSS Modules
modules: {
// 自定义生成的类名
localIdentName: '[local]_[hash:base64:8]'
}
}
}
]
},
// 这里匹配普通的 `<style>` 或 `<style scoped>`
{
use: [
'vue-style-loader',
'css-loader'
]
}
]
},
重启服务,页面能看到边框,border 生效,而且 module 的效果也还在。
和预处理器配合使用
CSS Modules 可以与其它预处理器一起使用。
我们尝试给 less 增加 css modules。
给 <style module>
块增加 lang='less'
,并添加 less 语句:
<style module lang='less'>
...
.bold {
font-weight: bold;
}
/* less 语法 */
@italic: italic;
p{
font-style: italic
}
</style>
页面中文字全部变成斜体,但 css module 定义的红色、加粗效果都没了。
修改配置文件:
// webpack.config.js -> module.rules
{
test: /\.less$/,
use: [
'vue-style-loader',
// +
{
loader: 'css-loader',
options: {
// 开启 CSS Modules
modules: {
localIdentName: '[local]_[hash:base64:8]'
}
}
},
// postcssLoader 可以参考本文末尾的“核心代码”
postcssLoader,
'less-loader'
]
},
重启服务器,less 和 css module 都生效了。
自定义的注入名称
在 .vue 中你可以定义不止一个 <style>
,为了避免被覆盖,你可以通过设置 module 属性来为它们定义注入后计算属性的名称。就像这样:
<style module="a">
/* 注入标识符 a */
</style>
<style module="b">
/* 注入标识符 b */
</style>
将 App.vue 的内容修改为:
<script>
export default {
created () {
console.log(this.a)
console.log(this.$style)
}
}
</script>
<style module='a' >
.a {}
.c1 {
color: red;
}
</style>
<style module>
.c1 {
color: blue;
}
</style>
这段代码定义了一个默认的 module 以及一个名为 a 的 module,浏览器控制台输出:
{a: "a_132IjK4h", c1: "c1_R9Fj2CxU"}
{c1: "c1_R9Fj2CxU"}
热重载
“热重载”不只是当你修改文件的时候简单重新加载页面。启用热重载后,当你修改 .vue 文件时,该组件的所有实例将在不刷新页面的情况下被替换。它甚至保持了应用程序和被替换组件的当前状态!当你调整模版或者修改样式时,这极大地提高了开发体验。
Tip: 与”webpack 快速入门 系列 —— 性能“一文中的热模块差不多意思,所以有关热模块的细节这里就不在复述。
状态保留规则
当编辑一个组件的 <template>
时,这个组件实例将就地重新渲染,并保留当前所有的私有状态。能够做到这一点是因为模板被编译成了新的无副作用的渲染函数。
当编辑一个组件的 <script>
时,这个组件实例将就地销毁并重新创建。(应用中其它组件的状态将会被保留) 是因为 <script>
可能包含带有副作用的生命周期钩子,所以将重新渲染替换为重新加载是必须的,这样做可以确保组件行为的一致性。这也意味着,如果你的组件带有全局副作用,则整个页面将会被重新加载。
<style>
会通过 vue-style-loader
自行热重载,所以它不会影响应用的状态。
注:”如果你的组件带有全局副作用,则整个页面将会被重新加载“未测试出来
用法
当使用脚手架工具 vue-cli 时,热重载是开箱即用的。
当手动设置你的工程时,热重载会在你启动 webpack-dev-server --hot 服务时自动开启。
现在我们没有开启热模块,所以修改代码浏览器就会刷新。你可以尝试将:
<template>
<div>1</div>
</template>
改为:
<template>
<div>12</div>
</template>
而倘若开启热模块,就像这样:
// webpack.config.js
module.exports = {
devServer: {
hot: true,
}
}
重启服务器,再次修改代码,浏览器则不会在刷新就能响应我们的更改。
关闭热重载
热重载默认是开启的,除非遇到以下情况:
- webpack 的 target 的值是 node (服务端渲染)
- webpack 会压缩代码
- process.env.NODE_ENV === 'production'
测试一下最后一条规则:
// webpack.config.js
const process = require('process');
process.env.NODE_ENV = 'production'
重启服务,修改代码,发现热模块果然失效。
还可以通过 hotReload 显式地关闭热重载:
// webpack.config.js -> module.rules
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
hotReload: false // 关闭热重载
}
},
Tip:建议开启热模块替换,方便后续测试。
函数式组件
函数式组件无状态(没有响应式数据),也没有实例(没有 this 上下文)。一个函数式组件就像这样:
Vue.component('my-component', {
functional: true,
// Props 是可选的
props: {
// ...
},
// 为了弥补缺少的实例
// 提供第二个参数作为上下文
render: function (createElement, context) {
// ...
}
})
在一个 *.vue 文件中以单文件形式定义的函数式组件,现在对于模板编译、scoped CSS 和热重载也有了良好的支持。
要声明一个应该编译为函数式组件的模板,请将 functional 特性添加到模板块中。这样做以后就可以省略 <script>
块中的 functional 选项。请看示例:
首先定义一个函数式组件:
// Box.vue
<template functional>
<div>
<!-- props:提供所有 prop 的对象 -->
{{ props.foo }}
<!-- parent 对父组件的引用 -->
<p>{{parent.$data.msg}}</p>
</div>
</template>
接着在 App.vue 中使用 Box.vue:
// App.vue
<template>
<div>
<Box foo='1'/>
</div>
</template>
<script>
import Box from './Box.vue'
export default {
data () {
return {
msg: '2',
}
},
components: {
Box
}
}
</script>
页面显示:
1
2
自定义块
在 .vue 文件中,你可以自定义语言块。应用于一个自定义块的 loader 是基于这个块的 lang 特性、块的标签名以及你的 webpack 配置进行匹配的。
如果指定了一个 lang 特性,则这个自定义块将会作为一个带有该 lang 扩展名的文件进行匹配。
你也可以使用 resourceQuery 来为一个没有 lang 的自定义块匹配一条规则。如果这个自定义块被所有匹配的 loader 处理之后导出一个函数作为最终结果,则这个 *.vue 文件的组件会作为一个参数被这个函数调用。
Example
这里创建一个 <docs>
自定义块。
为了注入自定义块的内容,我们先写一个自定义 loader:
// src/docs-loader.js
module.exports = function (source, map) {
this.callback(
null,
`export default function (Component) {
Component.options.__docs = ${
JSON.stringify(source)
}
}`,
map
)
}
Tip: loader 本质上是导出为函数的 JavaScript 模块。有关自定义 loader 更多介绍请看我的另一篇文章”webpack 快速入门 系列 - 自定义 webpack 上“。
接着我们给 <docs>
自定义块配置上自定义 loader。
// wepback.config.js -> module.rules
{
resourceQuery: /blockType=docs/,
loader: require.resolve('./src/docs-loader.js')
},
接下来在 Box.vue 中使用 <docs>
:
<template>
<div>Hello</div>
</template>
<docs>
i am docs
</docs>
然后在 App.vue 中引入 Box.vue,然后输出 docs 中的内容:
<template>
<div>
<Box/>
<p>{{ docs }}</p>
</div>
</template>
<script>
import Box from './Box.vue'
export default {
data () {
return {
docs: Box.__docs
}
},
components: {
Box
}
}
</script>
页面输出:
Hello
i am docs
<docs>
自定义块中的内容被成功输出。
CSS 提取
Tip:请只在生产环境下使用 CSS 提取;这里针对的是 webpack 4,而非 webpack 3。
先安装依赖,然后修改配置:
npm i -D mini-css-extract-plugin@1
// webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
...
module: {
rules: [
{
test: /\.css$/,
oneOf: [
...
{
use: [
process.env.NODE_ENV !== 'production'
? 'vue-style-loader'
: MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
},
],
},
plugins: [
new MiniCssExtractPlugin(),
...
],
};
App.vue:
// App.vue
<template>
<p>i am red?</p>
</template>
<style>
p{color:red}
</style>
重启服务器,通过浏览器查看,样式在 main.css 中。
注:别忘了将 process.env.NODE_ENV = 'production'
开启,否则不会提取 css,跟 mode: 'development'
没有关系。
代码校验 (Linting)
ESLint
引入 javascript 语法校验,配置如下:
Tip: 参考”webpack 快速入门 系列 —— 实战一->js 语法检查“
// eslint-loader废弃了,故使用 eslint-webpack-plugin
> npm i -D eslint@7 eslint-webpack-plugin@2 eslint-config-airbnb-base@14
// webpack.config.js
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
plugins: [
new ESLintPlugin({
// 将启用ESLint自动修复功能。此选项将更改源文件
fix: true
})
],
};
eslint 的配置文件:
// .eslintrc.js
module.exports = {
"extends": "airbnb-base",
"rules": {
"no-console": "off"
},
"env": {
"browser": true
}
}
重启服务,终端报错:
ERROR in
test-vue-loader\src\index.js
1:1 error 'vue' should be listed in the project's dependencies, not devDependencies import/no-extraneous-dependencies
5:1 error Do not use 'new' for side effects no-new
✖ 2 problems (2 errors, 0 warnings)
修复错误1,通过给 .eslintrc.js 增加一条 rule
// .eslintrc.js module.exports -> rules
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}]
修复错误2,在使用 new 的句子上添加/* eslint-disable no-new */
注释来绕开语法检查
// index.js
/* eslint-disable no-new */
new Vue({
...
});
重启服务,终端不在抛出错误。
给 index.js 增加如下 js 代码用于检验:
// index.js
new Vue({
...
});
// 很多连续空格
function sum(a, b) {
return a + b;
}
console.log(sum(1, 10))
不到三秒,连续的空格就会被合并。效果如下:
...
function sum(a, b) {
return a + b;
}
// 除了空格被合并,末尾还自动加上了分号
console.log(sum(1, 10));
对 App.vue 进行相同的测试,却没有触发校验,即没有自动合并连续空格。
猜测应该是 eslint 只配置了 js,需要将 vue 也配置上:
// webpack.config.js
new ESLintPlugin({
// 默认是 js,再增加 vue
extensions: ['js', 'vue'],
fix: true
})
重启服务,终端报错:
ERROR in
App.vue
1:1 error Parsing error: Unexpected token <
✖ 1 problem (1 error, 0 warnings)
于是决定尝试使用 eslint-plugin-vue(Vue.js 的官方 ESLint 插件)来解决此问题。
安装依赖包:
> npm i -D eslint-plugin-vue@7
修改 .eslintrc.js 的 extends 值:
module.exports = {
"extends": [
"airbnb-base",
"plugin:vue/essential"
],
// "extends": "airbnb-base",
...
}
重启服务,再次修改 App.vue 中的 js,则也会自动校验(例如合并连续空格)。
stylelint
尝试给样式增加多个空格:
// App.vue
<style>
/* 多个空格 */
.example {
color: red;
font-size: 2em;
}
</style>
发现样式中的空格没有自动合并,应该需要进行 stylelint 的配置。
Tip:这里就不展开,请自行研究哈。
单文件组件规范
简介
.vue 文件是一个自定义的文件类型,用类 HTML 语法描述一个 Vue 组件。每个 .vue 文件包含三种类型的顶级语言块 <template>
、<script>
和 <style>
,还允许添加可选的自定义块。
<template>
<div class="example">{{ msg }}</div>
</template>
<script>
export default {
data () {
return {
msg: 'Hello world!'
}
}
}
</script>
<style>
.example {
color: red;
}
</style>
<custom1>
This could be e.g. documentation for the component.
</custom1>
vue-loader 会解析文件,提取每个语言块,如有必要会通过其它 loader 处理,最后将他们组装成一个 ES Module,它的默认导出是一个 Vue.js 组件选项的对象。
vue-loader 支持使用非默认语言,比如 CSS 预处理器,预编译的 HTML 模版语言,通过设置语言块的 lang 属性。例如,你可以像下面这样使用 Sass 语法编写样式:
<style lang="sass">
/* write Sass! */
</style>
语言块
模板
- 每个 .vue 文件最多包含一个
<template>
块。 - 内容将被提取并传递给 vue-template-compiler 为字符串,预处理为 JavaScript 渲染函数,并最终注入到从
<script>
导出的组件中
如果在一个 vue 文件中包含多个 <template>
块会怎么样?
给 App.vue 添加两个模板:
<template>
<div class="example">第一个 {{ msg }}</div>
</template>
<template>
<div class="example">第二个 {{ msg }}</div>
</template>
...
浏览器页面显示“第二个 Hello world!”。终端和浏览器控制台没有报错。
脚本
- 每个 .vue 文件最多包含一个
<script>
块。 - 这个脚本会作为一个 ES Module 来执行。
- 它的默认导出应该是一个 Vue.js 的组件选项对象。也可以导出由 Vue.extend() 创建的扩展对象,但是普通对象是更好的选择。
- 任何匹配 .js 文件 (或通过它的 lang 特性指定的扩展名) 的 webpack 规则都将会运用到这个
<script>
块的内容中。
我们逐一分析上述规则。
如果一个 vue 文件包含多个 <script>
块会怎么样?
给 App.vue 写入2个 script 块:
<template>
<div class="example">{{ msg }}</div>
</template>
<script>
export default {
data () {
return {
msg: 'Hello world!'
}
}
}
</script>
<script>
console.log('第二个 script');
</script>
...
页面空白,只有控制台输出“第二个 script”。控制台和终端也没有报错。将 script 块调换,浏览器页面输出“Hello world!”
“它的默认导出应该是一个 Vue.js 的组件选项对象”什么意思?
在 vue 官网学习时,定义一个名为 button-counter 的新组件会这么写:
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
在单页面组件中得这么写:
<template>
<button v-on:click="count++">You clicked me {{ count }} times.</button>
</template>
<script>
export default {
data: function () {
return {
count: 0
}
},
};
</script>
首先将 Vue.component() 的第二个参数作为默认导出,然后把 template 的值(<button ...
)放到 template 块中。
样式
- 默认匹配:/.css$/。
- 一个 .vue 文件可以包含多个
<style>
标签。 <style>
标签可以有 scoped 或者 module 属性 (查看 scoped CSS和 CSS Modules) 以帮助你将样式封装到当组件。具有不同封装模式的多个<style>
标签可以在同一个组件中混合使用。- 任何匹配 .css 文件 (或通过它的 lang 特性指定的扩展名) 的 webpack 规则都将会运用到这个
<style>
块的容中
自定义块
可以在 .vue 文件中添加额外的自定义块来实现项目的特定需求,例如 <docs>
块。vue-loader 将会使用标签名来查找对应的 webpack loader 来应用在对应的块上。webpack loader 需要在 vue-loader 的选项 loaders 中指定。
Src导入
如果喜欢把 .vue 文件分隔到多个文件中,你可以通过 src 属性导入外部文件。
例如将 App.vue 改造成 src 导入模式:
// App.vue
<template src='./AppComponent/template.html'></template>
<script src='./AppComponent/script.js'></script>
...
// AppComponent/script.js
export default {
data () {
return {
msg: 'Hello world! ph'
}
}
}
// AppComponent/template.html
<div class="example">{{ msg }}</div>
运行后,与改造之前的效果一样。
注释
在语言块中使用该语言块对应的注释语法 (HTML、CSS、JavaScript、Jade 等)。顶层注释使用 HTML 注释语法:<!-- comment contents here -->
。请看示例:
<template>
<div class="example">
<!-- html 注释 -->
{{ msg }}
</div>
</template>
<!-- 顶层注释使用 HTML 注释 -->
<!--
<template>
<div>
第二个 template
</div>
</template>
-->
<script>
export default {
data () {
return {
msg: 'Hello world! ph'
}
}
}
// js 单行注释
/* 块注释 */
console.log('11');
</script>
<style>
.example {
color: red;
/* 字号:2em */
font-size: 2em;
}
</style>
总结
至此,通过本篇文章,我们学会了编写一个简单的,用于单文件组件开发的脚手架。包括:
- 单文件组件的格式编写 vue 组件
- 图片
- 预处理器:sass、less、stylus、postcss、babel、typescript、pug
- scoped和css module
- 热重载
- 函数式组件
- 自定义块
- css 提取
- 代码校验
核心代码
附上项目最终核心文件,方便学习和解惑。
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const {
VueLoaderPlugin
} = require('vue-loader');
const process = require('process');
process.env.NODE_ENV = 'production'
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ESLintPlugin = require('eslint-webpack-plugin');
const postcssLoader = {
loader: 'postcss-loader',
options: {
// postcss 只是个平台,具体功能需要使用插件
postcssOptions:{
plugins:[
[
"postcss-preset-env",
{
browsers: 'ie >= 8, chrome > 10',
},
],
]
}
}
}
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
/*
{
test: /\.css$/i,
use: ["style-loader", "css-loader"]
},
*/
/*
{
test: /\.css$/i,
use: [
"style-loader",
{
loader: 'css-loader',
options: {
// 开启 CSS Modules
modules: {
// 自定义生成的类名
localIdentName: '[local]_[hash:base64:8]'
}
}
}
]
},
*/
{
test: /\.css$/,
oneOf: [
// 这里匹配 `<style module>`
{
resourceQuery: /module/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
// 开启 CSS Modules
modules: {
// 自定义生成的类名
localIdentName: '[local]_[hash:base64:8]'
}
}
}
]
},
// 这里匹配普通的 `<style>` 或 `<style scoped>`
/*
{
use: [
'vue-style-loader',
'css-loader'
]
}
*/
{
use: [
process.env.NODE_ENV !== 'production'
? 'vue-style-loader'
: MiniCssExtractPlugin.loader,
'css-loader'
]
},
]
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
hotReload: true // 关闭热重载
}
},
{
test: /\.(png|jpg|gif)$/i,
use: [{
loader: 'url-loader',
options: {
// 调整的比 6.68 要小,这样图片就不会打包成 base64
limit: 1024 * 6,
esModule: false,
},
}, ],
},
{
test: /\.scss$/,
use: [
'vue-style-loader',
'css-loader',
'sass-loader'
]
},
{
test: /\.sass$/,
use: [
'vue-style-loader',
'css-loader',
{
loader: 'sass-loader',
options: {
// sass-loader version >= 8
sassOptions: {
indentedSyntax: true
},
additionalData: `$size: 3em;`,
}
}
]
},
/*
{
test: /\.less$/,
use: [
'vue-style-loader',
'css-loader',
postcssLoader,
'less-loader'
]
},
*/
{
test: /\.less$/,
use: [
'vue-style-loader',
// +
{
loader: 'css-loader',
options: {
// 开启 CSS Modules
modules: {
localIdentName: '[local]_[hash:base64:8]'
}
}
},
postcssLoader,
'less-loader'
]
},
{
test: /\.styl(us)?$/,
use: [
'vue-style-loader',
'css-loader',
'stylus-loader'
]
},
{
test: /\.js$/,
// exclude: /node_modules/,
exclude: file => (
/node_modules/.test(file) &&
!/\.vue\.js/.test(file)
),
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env']
]
}
}
},
{
test: /\.ts$/,
loader: 'ts-loader',
options: { appendTsSuffixTo: [/\.vue$/] }
},
{
test: /\.pug$/,
loader: 'pug-plain-loader'
},
{
resourceQuery: /blockType=docs/,
loader: require.resolve('./src/docs-loader.js')
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new VueLoaderPlugin(),
new MiniCssExtractPlugin(),
new ESLintPlugin({
extensions: ['js', 'vue'],
// 将启用ESLint自动修复功能。此选项将更改源文件
fix: true
})
],
mode: 'development',
devServer: {
hot: true,
// open: true,
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 9000,
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src/'),
},
extensions: ['.ts', '.js'],
},
};
package.json
{
"name": "test-vue-loader",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"dev": "webpack-dev-server"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/preset-env": "^7.14.7",
"babel-loader": "^8.2.2",
"css-loader": "^5.2.4",
"eslint": "^7.30.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-vue": "^7.12.1",
"eslint-webpack-plugin": "^2.5.4",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^4.5.2",
"less": "^4.1.1",
"less-loader": "^7.3.0",
"mini-css-extract-plugin": "^1.6.2",
"node-sass": "^6.0.1",
"postcss-loader": "^4.3.0",
"postcss-preset-env": "^6.7.0",
"pug": "^3.0.2",
"pug-plain-loader": "^1.1.0",
"sass-loader": "^10.2.0",
"style-loader": "^2.0.0",
"stylus": "^0.54.8",
"stylus-loader": "^4.3.3",
"ts-loader": "^7.0.5",
"typescript": "^4.3.5",
"url-loader": "^4.1.1",
"vue": "^2.6.14",
"vue-loader": "^15.9.7",
"vue-template-compiler": "^2.6.14",
"webpack": "^4.46.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.2"
}
}
其他章节请看:
出处:https://www.cnblogs.com/pengjiali/p/14971073.html
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。