Nuxt.js のコンポーネント
Nuxt.js は、ソースファイルのフォルダ構造を決めてくれるので、整理が楽でいいですよね。
Vue コンポーネントは、 components
フォルダに置くルールになっていますが、
自動で読み込んでくれるわけではありません。
例えば、 components
フォルダがこんな構造であった場合:
$ tree components/
components/
├── Logo.vue
├── README.md
├── VuetifyLogo.vue
└── app
└── welcome
└── MessageBoard.vue
2 directories, 4 files
vue
コンポーネントにはこんな感じで書くと思います。
<template>
<app-welcome-message-board>
<div>今日はいい天気ですね!</div>
</app-welcome-message-board>
</template>
<script>
import AppWelcomeMessageBoard from '@/components/app/welcome/MessageBoard'
export default {
components: {
AppWelcomeMessageBoard
}
}
</script>
つらたん。。。
コンポーネントを自動で import
してほしい!
import
文と components
フィールドに定義するのは冗長で面倒です。
上記は1つのコンポーネントの例ですが、
コンポーネント数が多くなると管理がかなり煩雑になります。
解決アプローチは、2つあります。
Vue.component
でグローバルに登録する
plugins
フォルダに JavaScript ファイルを作って、
最初に全てのコンポーネントを登録しておく方法です。
import Vue from 'vue'
import AppWelcomeMessageBoard from '@/components/app/welcome/MessageBoard'
Vue.component('app-welcome-message-board', AppWelcomeMessageBoard)
1行ずつコンポーネントを登録していくこともできますが、
数が増えてきたらちょっと省力化するのもアリです。
import Vue from 'vue'
import AppWelcomeMessageBoard from '@/components/app/welcome/MessageBoard'
const components = {
AppWelcomeMessageBoard
}
Object.entries(components).forEach(([name, component]) => {
Vue.component(name, component)
})
Webpack 配下なので、 require.context
を使って省力化する方法もあります。
import path from 'path'
import Vue from 'vue'
const components = require.context(
'~/components',
true,
/\.(vue|js)$/
)
function camelCase (...args) {
return args.map(part => part.slice(0, 1).toUpperCase() + part.slice(1)).join('')
}
components.keys().forEach((fileName) => {
const extName = path.extname(fileName)
const dirName = path.dirname(fileName).split('/').filter((part, index) => part !== '.')
const baseName = path.basename(fileName, extName)
const componentName = camelCase(...dirName, baseName)
const componentObj = components(fileName)
Vue.component(componentName, componentObj.default || componentObj)
})
デメリット
しかし、この方法には欠点があります。
-
components
フォルダに置いてあるものの、実際にはどこからも参照されていないコンポーネントが、
一緒にビルドされてしまう - 全てのページで、全てのコンポーネントをメモリに展開してしまう
VuetifyLoaderPlugin
で treeShake
する際に import
する
Vuetify.js をフレームワークとして使用している場合は、
import AppWelcomeMessageBoard from '@/components/app/welcome/MessageBoard'
export default {
components: {
AppWelcomeMessageBoard
}
}
の部分を動的に挿入してビルドしてくれる、 vuetify-loader/lib/plugin
が利用できます。
Nuxt.js が >= 2.9.0
である場合は、 nuxt.config.js
の buildModules
を使って、
export default {
...
/*
** Nuxt.js dev-modules
*/
buildModules: [
'@nuxtjs/vuetify'
],
/*
** vuetify module configuration
** https://github.com/nuxt-community/vuetify-module
*/
vuetify: {
...
},
...
}
となっていると思います。
この vuetify
の設定の所を、
import path from 'path'
import glob from 'glob'
const COMPONENTS_DIR = 'components'
export default {
...
/*
** vuetify module configuration
** https://github.com/nuxt-community/vuetify-module
*/
vuetify: {
...
treeShake: {
loaderOptions: {
/**
* This function will be called for every tag used in each vue component
* It should return an array, the first element will be inserted into the
* components array, the second should be a corresponding import
*
* originalTag - the tag as it was originally used in the template
* kebabTag - the tag normalised to kebab-case
* camelTag - the tag normalised to PascalCase
* path - a relative path to the current .vue file
* component - a parsed representation of the current component
*/
match (originalTag, { kebabTag, camelTag }) {
const parts = kebabTag.split('-')
const REQUIRED_MIN_FILENAME = 3
if (parts[0].length >= REQUIRED_MIN_FILENAME) {
for (let i = 0; i < parts.length; i++) {
const pathPart = parts.slice(0, i)
const filePart = parts.slice(i)
const relPath = path.join(COMPONENTS_DIR, ...pathPart, camelTag.substr(-filePart.join('').length))
const globResult = glob.sync(`${relPath}.{js,vue}`, {
cwd: __dirname
})
if (globResult.length > 0) {
return [
camelTag,
`import ${camelTag} from '~/${relPath}'`
]
}
}
}
}
}
}
},
とすると、ローカルにファイルがあるかどうかを判定して、
必要な import
文を挿入してくれます。
デメリット
この方法の良くないところは、ビルド時にファイルシステムにアクセスするので、
時間がかかるところです。
キャッシュすると早くなると思いますが、それはまたの機会に。
Nuxt.js < 2.9.0
の場合
nuxt.config.js
の modules
に書きましょう。
@nuxtjs/vuetify
を使わない場合
この辺 に書かれていることを、自分で書きます。
const VuetifyLoaderPlugin = require('vuetify-loader/lib/plugin')
export default {
...
/*
** Build configuration
*/
build: {
transpile: [
'vuetify/lib'
],
/*
** You can extend webpack config here
*/
extend (config, ctx) {
config.plugins.push(new VuetifyLoaderPlugin({
match (originalTag, { kebabTag, camelTag }) {
...
}
})
}
}
}
ソースコード公開中
サンプルの Nuxt.js プロジェクトを GitHub で公開しておきました。
なお、 Nuxt.js のバージョンが上がっていくと、
いろいろ構成が変わっていく可能性があります。