编写 Nuxt 模块代码片段

在本主题中,我们将把已创建的自定义模块拆解为小型代码片段。

您可以在我们的 GitHub 仓库中的 /chapter-6/nuxt-universal/module-snippets/ 目录找到以下所有代码实现。

使用顶层选项

还记得我们在 "编写基础模块" 章节提到的可传入模块的配置选项吗?模块选项是在 Nuxt 配置文件中注册模块时的顶级选项。我们甚至可以组合不同模块的多个选项,并实现选项共享。让我们通过以下步骤尝试结合使用 @nuxtjs/axios@nuxtjs/proxy 模块的示例:

  1. 使用 npm 同时安装这两个模块:

    $ npm i @nuxtjs/axios
    $ npm i @nuxtjs/proxy
    bash

    这两个模块深度整合可避免 CORS 问题(我们将在后续开发跨域应用时详细讨论)。虽然无需手动注册 @nuxtjs/proxy 模块,但必须确保其存在于 package.json 的依赖项中。

  2. Nuxt 配置文件中注册 @nuxtjs/axios 模块并设置两个模块的顶级选项:

    // nuxt.config.js
    export default {
      modules: [
        '@nuxtjs/axios'
      ],
      axios: {
        proxy: true
      },
      proxy: {
        '/api/': {
          target: 'https://jsonplaceholder.typicode.com/',
          pathRewrite: {'^/api/': ''}
        },
      }
    }
    javascript

    axios 自定义选项中的 proxy: true 会启用 @nuxtjs/proxy 模块。proxy 选项中的 /api/: {...} 配置指示 @nuxtjs/axios 模块将 https://jsonplaceholder.typicode.com/ 作为 API 服务器目标地址,而 pathRewrite 选项会在 HTTP 请求时移除地址中的 /api/ 前缀(因为目标 API 不存在该路由)。

  3. 在任意组件中无缝使用:

    // pages/index.vue
    <template>
      <ul>
        <li v-for="user in users">
          {{ user.name }}
        </li>
      </ul>
    </template>
    <script>
    export default {
      async asyncData({ $axios }) {
        const users = await $axios.$get('/api/users')
        return { users }
      }
    }
    </script>
    vue

通过这种组合使用方式,我们只需书写简化的 API 地址(如 /api/users 替代完整的 https://jsonplaceholder.typicode.com/users )。这使得代码更加整洁,无需每次调用都书写完整 URL。注意配置的 /api/ 前缀会在所有 API 请求中自动添加,因此需要通过 pathRewrite 在发送请求时移除(如前文所述)。

更多关于这两个模块的顶级选项信息可参考:

  • @nuxtjs/axios:https://axios.nuxtjs.org/options

  • @nuxtjs/proxy:https://github.com/nuxt-community/proxymodule

本示例代码片段存放于 GitHub 仓库的 /chapter-6/nuxt-universal/module-snippets/top-level/ 目录。

使用 addPlugin 辅助函数

还记得在 "编写基础模块" 章节中提到的 ModuleContainer 实例和可通过 this 关键字访问的 this.addPlugin 辅助方法吗?在以下示例中,我们将创建一个通过该辅助方法提供 bootstrap-vue 插件的模块,该插件将被注册到 Vue 实例。让我们通过以下步骤创建这个模块片段:

  1. 安装 BootstrapBootstrapVue

    $ npm i bootstrap-vue
    $ npm i bootstrap
    bash
  2. 创建插件文件来导入 vuebootstrap-vue,并使用 use 方法注册 bootstrap-vue

    // modules/bootstrap/plugin.js
    import Vue from 'vue'
    import BootstrapVue from 'bootstrap-vue/dist/bootstrap-vue.esm'
    Vue.use(BootstrapVue)
    javascript
  3. 创建模块文件,使用 addPlugin 方法添加刚创建的插件文件:

    // modules/bootstrap/module.js
    import path from 'path'
    export default function (moduleOptions) {
      this.addPlugin(path.resolve(__dirname, 'plugin.js'))
    }
    javascript
  4. Nuxt 配置文件中添加该 bootstrap 模块的路径:

    // nuxt.config.js
    export default {
      modules: [
        ['~/modules/bootstrap/module']
      ]
    }
    javascript
  5. 在任意组件中使用 bootstrap-vue,例如创建一个切换警告文本的 Bootstrap 按钮:

    // pages/index.vue
    <b-button @click="toggle">
      {{ show ? '隐藏' : '显示' }} 警告
    </b-button>
    <b-alert v-model="show">
      你好 {{ name }}!
    </b-alert>
    
    import 'bootstrap/dist/css/bootstrap.css'
    import 'bootstrap-vue/dist/bootstrap-vue.css'
    export default {
      data () {
        return {
          name: 'BootstrapVue',
          show: true
        }
      }
    }
    vue

通过这个模块片段,我们无需在每次需要使用 bootstrap-vue 时都进行导入,因为它已通过上述模块全局添加。我们只需要导入其 CSS 文件。在使用示例中,我们使用 Bootstrap 的自定义 <b-button> 组件来切换 Bootstrap 的自定义 <b-alert> 组件。<b-button> 组件将切换按钮上的 "隐藏" 或 "显示" 文本。

有关 BootstrapVue 的更多信息,请访问 https://bootstrapvue.js.org/ 。您可以在我们的 GitHub 仓库的 /chapter-6/nuxt-universal/module-snippets/provide-plugin/ 目录中找到刚刚创建的示例模块片段。

使用 Lodash 模板

这再次呼应了我们在 "编写基础模块" 章节创建自定义模块时的做法——利用 Lodash 模板通过条件判断块来改变注册插件的输出行为。Lodash 模板正是通过 <%= %> 插值分隔符实现数据属性动态注入的代码块。让我们通过以下步骤尝试另一个简单示例:

  1. 创建插件文件导入 axios,并添加条件判断块确保为 axios 提供请求 URL,同时在开发模式 (npm run dev) 下将请求结果打印到终端用于调试:

    // modules/users/plugin.js
    import axios from 'axios'
    
    let users = []
    <% if (options.url) { %>
    users = axios.get('<%= options.url %>')
    <% } %>
    <% if (options.debug) { %>
    // 仅开发环境代码
    users.then((response) => {
      console.log(response);
    })
    .catch((error) => {
      console.log(error);
    })
    <% } %>
    
    export default ({ app }, inject) => {
      inject('getUsers', async () => {
        return users
      })
    }
    javascript
  2. 创建模块文件,使用 addPlugin 方法添加刚创建的插件文件,并通过 options 参数传递请求 URLthis.options.dev 布尔值:

    // modules/users/module.js
    import path from 'path'
    
    export default function (moduleOptions) {
      this.addPlugin({
        src: path.resolve(__dirname, 'plugin.js'),
        options: {
          url: 'https://jsonplaceholder.typicode.com/users',
          debug: this.options.dev
        }
      })
    }
    javascript
  3. Nuxt 配置文件中添加该模块路径:

    // nuxt.config.js
    export default {
      modules: [
        ['~/modules/users/module']
      ]
    }
    javascript
  4. 在任意组件中使用 $getUsers 方法,例如:

    // pages/index.vue
    <li v-for="user in users">
      {{ user.name }}
    </li>
    export default {
      async asyncData({ app }) {
        const { data: users } = await app.$getUsers()
        return { users }
      }
    }
    vue

在此示例中,Nuxt 在复制插件到项目时会用 https://jsonplaceholder.typicode.com/users 替换 options.url。而 options.debug 的条件判断块在生产构建时会被移除,因此在生产模式(npm run buildnpm run start)下不会在终端看到 console.log 输出。

该示例模块片段存放于 GitHub 仓库的 /chapter-6/nuxt-universal/module-snippets/template-plugin/ 目录。

添加 CSS 库

在《使用 addPlugin 辅助方法》章节的模块片段示例中,我们创建了一个全局集成 bootstrap-vue 插件的模块,使得组件中无需重复导入该插件,如下所示:

// pages/index.vue
<b-button size="sm" @click="toggle">
  {{ show ? '隐藏' : '显示' }} 警告
</b-button>

import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
export default {
  //...
}
vue

看起来相当简洁,因为我们不必每次都导入 bootstrap-vue,而只需要导入 CSS 样式。然而,我们仍然可以通过模块将样式添加到应用程序的全局 CSS 堆栈中来节省几行代码。让我们创建一个新的示例,看看如何通过以下步骤来实现:

  1. 创建一个模块文件,其中包含一个名为 options 的常量变量,用于将模块和顶级选项传递给插件文件,以及一个 if 条件块,用于确定是否使用普通的 JavaScript push 方法将 CSS 文件推送到 Nuxt 配置文件的 css 选项中:

    // modules/bootstrap/module.js
    import path from 'path'
    export default function (moduleOptions) {
      const options = Object.assign({}, this.options.bootstrap, moduleOptions)
      if (options.styles !== false) {
        this.options.css.push('bootstrap/dist/css/bootstrap.css')
        this.options.css.push('bootstrap-vue/dist/bootstrap-vue.css')
      }
      this.addPlugin({
        src: path.resolve(__dirname, 'plugin.js'),
        options
      })
    }
    javascript
  2. 创建插件文件注册 bootstrap-vue,并添加 Lodash 模板条件块打印模块处理的选项:

    // modules/bootstrap/plugin.js
    import Vue from 'vue'
    import BootstrapVue from 'bootstrap-vue/dist/bootstrap-vue.esm'
    
    Vue.use(BootstrapVue)
    <% if (options.debug) { %>
    <% console.log (options) %>
    <% } %>
    javascript
  3. Nuxt 配置中添加模块路径,通过 moduleOptions 控制 CSS 注入,并通过顶级选项传递 debug 模式状态:

    // nuxt.config.js
    export default {
      modules: [
        ['~/modules/bootstrap/module', { styles: true }]
      ],
      bootstrap: {
        debug: process.env.NODE_ENV === 'development' ? true : false
      }
    }
    javascript
  4. 移除组件中的 CSS 导入语句:

    // pages/index.vue
    <script>
    - import 'bootstrap/dist/css/bootstrap.css'
    - import 'bootstrap-vue/dist/bootstrap-vue.css'
    export default {
      //...
    }
    </script>
    vue

所以,最终,我们可以在我们的组件中使用 bootstrap-vue 插件及其 CSS 文件,而无需导入所有这些文件。这里是另一个通过模块代码片段将 Font Awesome CSS 选项推送到 Nuxt 配置文件中的快速示例:

// modules/bootstrap/module.js
export default function (moduleOptions) {
  if (moduleOptions.fontAwesome !== false) {
    this.options.css.push('font-awesome/css/font-awesome.css')
  }
}
javascript

更多关于 Font Awesome 的信息请访问: https://fontawesome.com/

完整示例代码存放于 GitHub 仓库的 /chapter-6/nuxt-universal/module-snippets/css-lib/ 目录。

注册自定义 webpack 加载器

当我们需要扩展 Nuxtwebpack 配置时,通常会在 nuxt.config.js 中使用 build.extend 实现。但通过模块中的 this.extendBuild 方法同样可以达成目标,以下是基础模板:

export default function (moduleOptions) {
  this.extendBuild((config, { isClient, isServer }) => {
    //...
  })
}
javascript

例如,假设我们需要通过 svg-transform-loader 扩展 webpack 配置(该 loader 用于修改 SVG 图像的标签和属性),主要功能包括填充颜色(fill)、描边(stroke)等操作。该 loader 支持在 CSS/Sass/Less/Stylus/PostCSS 中使用,例如:

/* 填充白色 */
.img {
  background-image: url('./img.svg?fill=fff');
}

/* Sass变量描边,如果你想在 Sass 中使用变量来描边 SVG 图像,你可以这样做: */
$stroke-color: fff;

.img {
  background-image: url('./img.svg?stroke={$stroke-color}');
}
css

让我们创建一个示例模块,将此加载器注册到 Nuxt webpack 的默认配置中,以便我们可以通过以下步骤在我们的 Nuxt 应用程序中操作 SVG 图像:

  1. 安装 loader

    $ npm i svg-transform-loader
    bash
  2. 创建模块文件:

    // modules/svg-transform-loader/module.js
    export default function (moduleOptions) {
      this.extendBuild((config, { isClient, isServer }) => {
        //...
      })
    }
    bash
  3. this.extendBuild 回调中移除默认 svg 规则:

    const rule = config.module.rules.find(
      r => r.test.toString() === '/\\.(png|jpe?g|gif|svg|webp)$/i'
    )
    rule.test = /\.(png|jpe?g|gif|webp)$/i
    javascript
  4. 在前面的代码块之后添加以下代码块,将 svg-transform-loader 加载器推送到默认 webpack 配置的模块规则中:

    config.module.rules.push({
      test: /\.svg(\?.*)?$/, // 匹配img.svg和img.svg?param=value
      use: [
        'url-loader',
        'svg-transform-loader'
      ]
    })
    javascript
  5. Nuxt 配置中注册模块:

    // nuxt.config.js
    export default {
      modules: [
        ['~/modules/svg-transform-loader/module']
      ]
    }
    javascript
  6. 在组件中使用 SVG 转换功能:

    <!-- pages/index.vue -->
    <template>
      <div>
        <div class="background"></div>
        <img src="~/assets/bug.svg?stroke=red&strokewidth=4&fill=blue">
      </div>
    </template>
    
    <style lang="less">
    .background {
      height: 100px;
      width: 100px;
      border: 4px solid red;
      background-image: url('~assets/bug.svg?stroke=red&strokewidth=2');
    }
    </style>
    vue

你可以在 https://www.npmjs.com/package/svg-transform-loader 找到更多关于 svg-transform-loader 的信息。如果你想了解更多关于规则测试的信息,并查看 Nuxt 默认 webpack 配置的完整内容,请访问以下链接:

你可以在我们的 GitHub 仓库的 /chapter-6/nuxt-universal/module-snippets/webpack-loader/ 找到我们刚刚创建的示例模块代码片段。

注册自定义 webpack 插件

Nuxt 模块不仅能注册 webpack 加载器,还能通过 this.options.build.plugins.push 注册 webpack 插件,其基础架构如下:

export default function (moduleOptions) {
  this.options.build.plugins.push({
    apply(compiler) {
      compiler.hooks.<hookType>.<tap>('<PluginName>', (param) => {
        //...
      })
    }
  })
}
javascript

其中 <tap> 依赖钩子类型,钩子类型可以是 tapAsynctapPromisetap。下面通过 Nuxt 模块创建一个简单的 "Hello World" webpack 插件:

  1. 使用我们提供的模块/插件架构创建一个模块文件,用于打印 "Hello World!",如下所示:

    // modules/hello-world/module.js
    export default function (moduleOptions) {
      this.options.build.plugins.push({
        apply(compiler) {
          compiler.hooks.done.tap('HelloWordPlugin', (stats) => {
            console.log('Hello World!')
          })
        }
      })
    }
    javascript

    注:当 done 钩子触发时,会传入 stats(统计信息)作为参数。

  2. Nuxt 配置中添加模块路径:

    // nuxt.config.js
    export default {
      modules: [
        ['~/modules/hello-world/module']
      ]
    }
    javascript
  3. 运行 npm run dev 后,终端将显示 "Hello World!"。

请注意,apply 方法、编译器(compiler)、钩子(hooks)和 taps 都是构建 webpack 插件的关键部分。

如果您是 webpack 插件的新手,并想了解更多关于如何开发 webpack 插件的信息,请访问 https://webpack.js.org/contribute/writing-a-plugin/。

您可以在我们的 GitHub 仓库中的 /chapter-6/nuxt-universal/module-snippets/webpack-plugin/ 路径下找到我们刚刚创建的示例模块片段。

在特定钩子上创建任务

如果你需要在 Nuxt 启动时,在特定的生命周期事件(例如,当所有模块加载完毕后)执行某些任务,你可以创建一个模块并使用 hook 方法来监听该事件,然后执行相应的任务。请看以下示例:

  • 如果你想在所有模块加载完成后执行某些操作,请尝试以下方法:

    export default function (moduleOptions) {
      this.nuxt.hook('modules:done', moduleContainer => {
        //...
      })
    }
    javascript
  • 如果你想在渲染器创建完成后执行某些操作,请尝试以下方法:

    export default function (moduleOptions) {
      this.nuxt.hook('render:before', renderer => {
        //...
      })
    }
    javascript
  • 在编译器(默认使用 webpack)启动前执行操作:

    export default function (moduleOptions) {
      this.nuxt.hook('build:compile', async ({ name, compiler }) => {
        //...
      })
    }
    javascript
  • Nuxt 生成页面之前执行操作:

    export default function (moduleOptions) {
      this.nuxt.hook('generate:before', async generator => {
        //...
      })
    }
    javascript
  • Nuxt 准备就绪时执行操作:

    export default function (moduleOptions) {
      this.nuxt.hook('ready', async nuxt => {
        //...
      })
    }
    javascript

以下步骤演示如何创建一个监听 modules:done 钩子的简单模块:

  1. 创建模块文件,在所有模块加载完成后打印 'All modules are loaded':

    // modules/tasks/module.js
    export default function (moduleOptions) {
      this.nuxt.hook('modules:done', moduleContainer => {
        console.log('All modules are loaded')
      })
    }
    javascript
  2. 创建其他模块,分别打印 'Module 1'、'Module 2' 等:

    // modules/module1.js
    export default function (moduleOptions) {
      console.log('Module 1')
    }
    javascript
  3. Nuxt 配置文件中添加钩子模块及其他模块路径:

    // nuxt.config.js
    export default {
      modules: [
        ['~/modules/tasks/module'],
        ['~/modules/module3'],
        ['~/modules/module1'],
        ['~/modules/module2']
      ]
    }
    javascript
  4. 运行 Nuxt 应用($npm run dev),终端将输出:

    Module 3
    Module 1
    Module 2
    All modules are loaded
    bash

可见钩子模块始终最后执行,其余模块按模块配置顺序执行。

钩子模块支持异步操作(可使用 async/await 或返回 Promise)。

更多关于 Nuxt 生命周期的钩子信息,请参考以下链接:

示例模块代码存放于 GitHub 仓库的 /chapter-6/nuxt-universal/module-snippets/hooks/ 目录中。