vue 快速入門 系列 —— vue loader 下

彭加李發表於2021-07-05

其他章節請看:

vue 快速入門 系列

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,
    }
}

重啟伺服器,再次修改程式碼,瀏覽器則不會在重新整理就能響應我們的更改。

關閉熱過載

熱過載預設是開啟的,除非遇到以下情況:

  1. webpack 的 target 的值是 node (服務端渲染)
  2. webpack 會壓縮程式碼
  3. 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"
  }
}

其他章節請看:

vue 快速入門 系列

相關文章