其他章節請看:
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"
}
}
其他章節請看: