├── test ├── fixture │ ├── components │ │ ├── foo.d.ts │ │ ├── multifile │ │ │ └── ComponentC │ │ │ │ ├── index.js │ │ │ │ └── ComponentC.vue │ │ ├── global │ │ │ ├── Mouse.vue │ │ │ ├── Deep │ │ │ │ └── Nested │ │ │ │ │ └── MyComponent.vue │ │ │ └── Big.async.vue │ │ ├── Foo.vue │ │ ├── Header.vue │ │ ├── form │ │ │ ├── input │ │ │ │ ├── text-area.vue │ │ │ │ ├── text │ │ │ │ │ └── text.vue │ │ │ │ └── FormInputRadio.vue │ │ │ └── layouts │ │ │ │ └── FormLayout.vue │ │ ├── icons │ │ │ └── Home.vue │ │ ├── NComponent.vue │ │ ├── 0-base │ │ │ ├── 1.Button.vue │ │ │ └── SecondButton │ │ │ │ ├── SecondButton.vue │ │ │ │ └── index.vue │ │ ├── no-prefix │ │ │ └── no-prefix1 │ │ │ │ └── index.vue │ │ ├── functional │ │ │ ├── FunctionalChild.vue │ │ │ └── Functional.vue │ │ ├── ui │ │ │ └── notification │ │ │ │ └── NotificationWrapper.vue │ │ └── Bar.js │ ├── theme │ │ └── components │ │ │ └── Header.vue │ ├── pages │ │ ├── no-template.vue │ │ ├── no-components.vue │ │ ├── global.vue │ │ ├── pug.vue │ │ ├── index.vue │ │ └── global-big.vue │ ├── my-lib │ │ └── components │ │ │ ├── bad.js │ │ │ └── MAwesome.js │ ├── tsconfig.json │ └── nuxt.config.ts ├── unit │ ├── compatibility.test.ts │ ├── matcher.test.ts │ ├── tagExtractor.test.ts │ ├── scanner.test.ts │ ├── utils.ts │ └── loader.test.ts └── module.test.ts ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── ci.yml ├── example ├── nuxt.config.js ├── components │ ├── ComponentA.vue │ ├── ComponentB.vue │ └── ComponentC.js ├── package.json └── pages │ └── index.vue ├── jest.config.js ├── .babelrc ├── .eslintignore ├── .eslintrc.js ├── renovate.json ├── templates ├── components │ ├── plugin.js │ ├── readme_md │ └── index.js └── vetur │ └── tags.json ├── .editorconfig ├── .gitignore ├── tsconfig.json ├── src ├── compatibility.ts ├── tagExtractor.ts ├── loader.ts ├── types.ts ├── scan.ts └── index.ts ├── LICENSE ├── lib └── installComponents.js ├── package.json ├── README.md └── CHANGELOG.md /test/fixture/components/foo.d.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [nuxt] 2 | -------------------------------------------------------------------------------- /test/fixture/components/multifile/ComponentC/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixture/components/multifile/ComponentC/ComponentC.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/nuxt.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | components: true 3 | } 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@nuxt/test-utils' 3 | } 4 | -------------------------------------------------------------------------------- /test/fixture/components/global/Mouse.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/env", { "targets": { "node": 10 }}] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixture/components/Foo.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/fixture/components/Header.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .nuxt 4 | coverage 5 | templates 6 | lib/installComponents.js 7 | -------------------------------------------------------------------------------- /test/fixture/theme/components/Header.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | '@nuxtjs/eslint-config-typescript' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /example/components/ComponentA.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /example/components/ComponentB.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/fixture/components/form/input/text-area.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /test/fixture/components/form/input/text/text.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /test/fixture/components/form/layouts/FormLayout.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /test/fixture/components/icons/Home.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/fixture/pages/no-template.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/fixture/components/NComponent.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/fixture/components/form/input/FormInputRadio.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /test/fixture/pages/no-components.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/fixture/components/0-base/1.Button.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/fixture/components/no-prefix/no-prefix1/index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/fixture/components/0-base/SecondButton/SecondButton.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/fixture/components/functional/FunctionalChild.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/fixture/components/global/Deep/Nested/MyComponent.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/fixture/components/0-base/SecondButton/index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/fixture/my-lib/components/bad.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-console 2 | console.log('woohooohoo my-lib has a tiny side-effect!') 3 | -------------------------------------------------------------------------------- /example/components/ComponentC.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export default Vue.extend({ 4 | render: h => h('div', 'Component C') 5 | }) 6 | -------------------------------------------------------------------------------- /test/fixture/components/functional/Functional.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/fixture/components/ui/notification/NotificationWrapper.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/fixture/my-lib/components/MAwesome.js: -------------------------------------------------------------------------------- 1 | // @vue/component 2 | export const MMAwesome = { 3 | render: h => h('div', 'M is Awesome!') 4 | } 5 | -------------------------------------------------------------------------------- /test/fixture/components/Bar.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export default Vue.extend({ 4 | render (h) { 5 | return h('div', 'Bar') 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@nuxtjs" 4 | ], 5 | "lockFileMaintenance": { 6 | "enabled": true 7 | }, 8 | "labels": [ 9 | "dependencies" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/fixture/pages/global.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /test/fixture/pages/pug.vue: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /templates/components/plugin.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import * as components from './index' 3 | 4 | for (const name in components) { 5 | Vue.component(name, components[name]) 6 | Vue.component('Lazy' + name, components[name]) 7 | } 8 | -------------------------------------------------------------------------------- /templates/vetur/tags.json: -------------------------------------------------------------------------------- 1 | <% 2 | const tags = {} 3 | for (const c of options.getComponents()) { 4 | tags[c.pascalName] = { 5 | description: `Auto imported from ${c.shortPath}` 6 | } 7 | } 8 | %><%= JSON.stringify(tags, null, 2) %> 9 | -------------------------------------------------------------------------------- /test/fixture/pages/index.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /test/fixture/pages/global-big.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = space 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "dependencies": { 4 | "nuxt-edge": "latest" 5 | }, 6 | "scripts": { 7 | "dev": "nuxt", 8 | "build": "nuxt build", 9 | "start": "nuxt start", 10 | "post-update": "yarn upgrade --latest" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/pages/index.vue: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /test/unit/compatibility.test.ts: -------------------------------------------------------------------------------- 1 | import { requireNuxtVersion } from '../../src/compatibility' 2 | 3 | test('should throw error if Nuxt version not supported', () => { 4 | expect(() => requireNuxtVersion('v2.9.0', '2.10')).toThrowError() 5 | expect(() => requireNuxtVersion('v2.11.0', '2.10')).not.toThrowError() 6 | expect(() => requireNuxtVersion(null, '2.10')).not.toThrowError() 7 | }) 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Only keep yarn.lock in the root 5 | */**/yarn.lock 6 | 7 | # Logs 8 | *.log 9 | 10 | # Other 11 | .nuxt* 12 | !.nuxtignore 13 | 14 | # Dist folders 15 | dist 16 | 17 | # Coverage reports 18 | coverage 19 | 20 | # VSCode 21 | .vscode 22 | 23 | # Intellij idea 24 | *.iml 25 | .idea 26 | 27 | # OSX 28 | .DS_Store 29 | .AppleDouble 30 | .LSOverride 31 | -------------------------------------------------------------------------------- /test/unit/matcher.test.ts: -------------------------------------------------------------------------------- 1 | import { matcher } from '../../src/scan' 2 | import { scanFixtureComponents } from './utils' 3 | 4 | test('matcher', async () => { 5 | const components = await scanFixtureComponents() 6 | const tags = ['Foo', 'BaseButton', 'IconHome'] 7 | 8 | const matchedComponents = matcher(tags, components).sort((a, b) => a.pascalName < b.pascalName ? -1 : 1) 9 | 10 | expect(matchedComponents).toHaveLength(tags.length) 11 | }) 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "outDir": "dist", 7 | "declaration": true, 8 | "esModuleInterop": true, 9 | "strict": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "noImplicitReturns": true, 12 | "stripInternal": true, 13 | "noUnusedLocals": true 14 | }, 15 | "include": [ 16 | "src" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /test/fixture/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "lib": [ 7 | "esnext", 8 | "esnext.asynciterable", 9 | "dom" 10 | ], 11 | "esModuleInterop": true, 12 | "sourceMap": true, 13 | "strict": true, 14 | "baseUrl": ".", 15 | "paths": { 16 | "~/*": [ 17 | "./*" 18 | ], 19 | "@/*": [ 20 | "./*" 21 | ] 22 | }, 23 | "types": [ 24 | "@nuxt/types" 25 | ] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/unit/tagExtractor.test.ts: -------------------------------------------------------------------------------- 1 | import path from 'upath' 2 | import { extractTags } from '../../src/tagExtractor' 3 | 4 | test('with template', async () => { 5 | const tags = await extractTags(path.resolve('test/fixture/pages/index.vue')) 6 | 7 | expect(tags).toEqual(['Header', 'Foo', 'LazyBar', 'BaseButton', 'IconHome', 'MAwesome', 'Functional', 'NComponent', 'div']) 8 | }) 9 | 10 | test('without template', async () => { 11 | const tags = await extractTags(path.resolve('test/fixture/pages/no-template.vue')) 12 | 13 | expect(tags).toHaveLength(0) 14 | }) 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Clone repository '...' 16 | 2. Run '....' 17 | 3. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Additional context** 26 | Add any other context about the problem here. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: feature request 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/compatibility.ts: -------------------------------------------------------------------------------- 1 | 2 | import chalk from 'chalk' 3 | import semver from 'semver' 4 | 5 | export function requireNuxtVersion (currentVersion?: string, requiredVersion?: string) { 6 | const pkgName = require('../package.json').name 7 | 8 | if (!currentVersion || !requireNuxtVersion) { 9 | return 10 | } 11 | 12 | const _currentVersion = semver.coerce(currentVersion)! 13 | const _requiredVersion = semver.coerce(requiredVersion)! 14 | 15 | if (semver.lt(_currentVersion, _requiredVersion)) { 16 | throw new Error(`\n 17 | ${chalk.cyan(pkgName)} is not compatible with your current Nuxt version : ${chalk.yellow('v' + currentVersion)}\n 18 | Required: ${chalk.green('v' + requiredVersion)} or ${chalk.cyan('higher')} 19 | `) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /templates/components/readme_md: -------------------------------------------------------------------------------- 1 | # Discovered Components 2 | 3 | This is an auto-generated list of components discovered by [nuxt/components](https://github.com/nuxt/components). 4 | 5 | You can directly use them in pages and other components without the need to import them. 6 | 7 | **Tip:** If a component is conditionally rendered with `v-if` and is big, it is better to use `Lazy` or `lazy-` prefix to lazy load. 8 | 9 | <% 10 | const components = options.getComponents() 11 | const list = components.map(c => { 12 | const pascalName = c.pascalName.replace(/^Lazy/, '') 13 | const kebabName = c.kebabName.replace(/^lazy-/, '') 14 | const tags = c.isAsync ? ' [async]' : '' 15 | return `- \`<${pascalName}>\` | \`<${kebabName}>\` (${c.shortPath})${tags}` 16 | }) 17 | %><%= list.join('\n') %> 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | ci: 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest, windows-latest] 17 | node: [14] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | 22 | - uses: actions/cache@v2 23 | id: cache 24 | with: 25 | path: "node_modules" 26 | key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }} 27 | 28 | - name: Install dependencies 29 | if: steps.cache.outputs.cache-hit != 'true' 30 | run: yarn 31 | 32 | - name: Run tests 33 | run: yarn test 34 | 35 | - name: Coverage 36 | uses: codecov/codecov-action@v2 37 | -------------------------------------------------------------------------------- /test/unit/scanner.test.ts: -------------------------------------------------------------------------------- 1 | import { warn, scanFixtureComponents } from './utils' 2 | 3 | test('scanner', async () => { 4 | const components = await scanFixtureComponents() 5 | 6 | const expectedComponents = [ 7 | 'BaseButton', 8 | 'ComponentC', 9 | 'BaseSecondButton', 10 | 'IconHome', 11 | 'Bar', 12 | 'Big', 13 | 'Mouse', 14 | 'NComponent', 15 | 'Foo', 16 | 'Functional', 17 | 'FunctionalChild', 18 | 'FormInputText', 19 | 'FormInputTextArea', 20 | 'FormInputRadio', 21 | 'FormLayout', 22 | 'Header', 23 | 'DeepNestedMyComponent', 24 | 'UiNotificationWrapper', 25 | 'NoPrefix1' 26 | ] 27 | 28 | expect(components.map(c => c.pascalName).sort()).toEqual(expectedComponents.sort()) 29 | 30 | expect(warn).toBeCalledWith( 31 | expect.stringMatching('Two component files resolving to the same name `BaseSecondButton`') 32 | ) 33 | }) 34 | -------------------------------------------------------------------------------- /src/tagExtractor.ts: -------------------------------------------------------------------------------- 1 | 2 | import { readFileSync } from 'fs' 3 | import { 4 | compile, 5 | parseComponent, 6 | ModuleOptions as CompilerModuleOptions 7 | } from 'vue-template-compiler' 8 | 9 | export async function extractTags (resourcePath: string): Promise { 10 | const tags = new Set() 11 | const file = (await readFileSync(resourcePath)).toString('utf8') 12 | const component = parseComponent(file) 13 | 14 | if (component.template) { 15 | if (component.template.lang === 'pug') { 16 | try { 17 | const pug = require('pug') 18 | component.template.content = pug.render(component.template.content, { filename: resourcePath }) 19 | } catch (err) { /* Ignore compilation errors, they'll be picked up by other loaders */ } 20 | } 21 | compile(component.template.content, { 22 | modules: [{ 23 | postTransformNode: (el) => { 24 | tags.add(el.tag) 25 | } 26 | } as CompilerModuleOptions] 27 | }) 28 | } 29 | 30 | return [...tags] 31 | } 32 | -------------------------------------------------------------------------------- /test/fixture/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { NuxtConfig } from '@nuxt/types' 3 | import nuxtComponents from '../../src' 4 | 5 | const config: NuxtConfig = { 6 | buildModules: [ 7 | '@nuxt/typescript-build', 8 | nuxtComponents 9 | ], 10 | 11 | typescript: { 12 | typeCheck: false 13 | }, 14 | 15 | components: [ 16 | '~/components', 17 | { path: '~/components/global', global: true, isAsync: false }, 18 | { path: '~/components/no-prefix', pathPrefix: false }, 19 | { path: '~/components/multifile', extensions: ['vue'] }, 20 | '~/non-existent', 21 | { path: '@/components/base', prefix: 'Base' }, 22 | { path: '@/components/icons', prefix: 'Icon', transpile: true /* Only for coverage purpose */ }, 23 | { path: '@/theme/components', level: 1 } 24 | ], 25 | 26 | hooks: { 27 | 'components:dirs' (dirs) { 28 | dirs.push({ 29 | prefix: 'm', 30 | path: path.resolve(__dirname, 'my-lib/components'), 31 | extendComponent: _c => ({ ..._c, export: _c.pascalName }) 32 | }) 33 | } 34 | } 35 | } 36 | 37 | export default config 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Nuxt.js Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/installComponents.js: -------------------------------------------------------------------------------- 1 | global.installComponents = function (component, components) { 2 | var options = typeof component.exports === 'function' 3 | ? component.exports.extendOptions 4 | : component.options 5 | 6 | if (typeof component.exports === 'function') { 7 | options.components = component.exports.options.components 8 | } 9 | 10 | options.components = options.components || {} 11 | 12 | for (var i in components) { 13 | options.components[i] = options.components[i] || components[i] 14 | } 15 | 16 | 17 | if (options.functional) { 18 | provideFunctionalComponents(component, options.components) 19 | } 20 | } 21 | 22 | var functionalPatchKey = '_functionalComponents' 23 | 24 | function provideFunctionalComponents(component, components) { 25 | if (component.exports[functionalPatchKey]) { 26 | return 27 | } 28 | component.exports[functionalPatchKey] = true 29 | 30 | var render = component.exports.render 31 | component.exports.render = function (h, vm) { 32 | return render(h, Object.assign({}, vm, { 33 | _c: function (n, a, b) { 34 | return vm._c(components[n] || n, a, b) 35 | } 36 | })) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/loader.ts: -------------------------------------------------------------------------------- 1 | import type { loader as WebpackLoader } from 'webpack' 2 | import { extractTags } from './tagExtractor' 3 | import { matcher } from './scan' 4 | import type { Component } from './types' 5 | 6 | function install (this: WebpackLoader.LoaderContext, content: string, components: Component[]) { 7 | const imports = '{' + components.map(c => `${c.pascalName}: ${c.isAsync ? c.asyncImport : c.import}`).join(',') + '}' 8 | 9 | let newContent = '/* nuxt-component-imports */\n' 10 | newContent += `installComponents(component, ${imports})\n` 11 | 12 | // Insert our modification before the HMR code 13 | const hotReload = content.indexOf('/* hot reload */') 14 | if (hotReload > -1) { 15 | content = content.slice(0, hotReload) + newContent + '\n\n' + content.slice(hotReload) 16 | } else { 17 | content += '\n\n' + newContent 18 | } 19 | 20 | return content 21 | } 22 | 23 | export default async function loader (this: WebpackLoader.LoaderContext, content: string) { 24 | this.async() 25 | this.cacheable() 26 | 27 | if (!this.resourceQuery) { 28 | this.addDependency(this.resourcePath) 29 | 30 | const { getComponents } = this.query 31 | const nonAsyncComponents = getComponents().filter((c: Component) => c.isAsync !== true) 32 | 33 | const tags = await extractTags(this.resourcePath) 34 | const matchedComponents = matcher(tags, nonAsyncComponents) 35 | 36 | if (matchedComponents.length) { 37 | content = install.call(this, content, matchedComponents) 38 | } 39 | } 40 | 41 | this.callback(null, content) 42 | } 43 | -------------------------------------------------------------------------------- /test/unit/utils.ts: -------------------------------------------------------------------------------- 1 | import path from 'upath' 2 | import { scanComponents } from '../../src/scan' 3 | 4 | export const warn = console.warn = jest.fn() // eslint-disable-line no-console 5 | 6 | // TODO: This is a dummy test! Actual array is in src/index 7 | const ignorePatterns = [ 8 | '**/*.stories.{js,ts,jsx,tsx}', // ignore storybook files 9 | '**/*{M,.m,-m}ixin.{js,ts,jsx,tsx}', // ignore mixins 10 | '**/*.d.ts' // .d.ts files 11 | ] 12 | 13 | export function scanFixtureComponents () { 14 | const srcDir = path.resolve('test/fixture') 15 | return scanComponents([ 16 | { 17 | path: path.resolve(srcDir, 'components'), 18 | pattern: '**/*.{vue,js,ts}', 19 | ignore: ignorePatterns 20 | }, 21 | { 22 | path: path.resolve(srcDir, 'components/base'), 23 | pattern: '**/*.{vue,js,ts}', 24 | prefix: 'base', 25 | ignore: ignorePatterns 26 | }, 27 | { 28 | path: path.resolve(srcDir, 'components/no-prefix'), 29 | pattern: '**/*.{vue,js,ts}', 30 | ignore: ignorePatterns, 31 | pathPrefix: false 32 | }, 33 | { 34 | path: path.resolve(srcDir, 'components/global'), 35 | pattern: '**/*.{vue,js,ts}', 36 | ignore: ignorePatterns 37 | }, 38 | { 39 | path: path.resolve(srcDir, 'components/multifile'), 40 | pattern: '**/*.{vue,js,ts}', 41 | ignore: ignorePatterns 42 | }, 43 | { 44 | path: path.resolve(srcDir, 'components/icons'), 45 | pattern: '**/*.{vue,js,ts}', 46 | prefix: 'icon', 47 | ignore: ignorePatterns 48 | } 49 | ], srcDir) 50 | } 51 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export interface Component { 2 | pascalName: string 3 | kebabName: string 4 | import: string 5 | asyncImport: string 6 | export: string 7 | filePath: string 8 | shortPath: string 9 | isAsync?: boolean 10 | chunkName: string 11 | /** @deprecated */ 12 | global: boolean 13 | level: number 14 | prefetch: boolean 15 | preload: boolean 16 | } 17 | 18 | export interface ScanDir { 19 | path: string 20 | pattern?: string | string[] 21 | ignore?: string[] 22 | prefix?: string 23 | isAsync?: boolean 24 | /** @deprecated */ 25 | global?: boolean | 'dev' 26 | pathPrefix?: boolean 27 | level?: number 28 | prefetch?: boolean 29 | preload?: boolean 30 | extendComponent?: (component: Component) => Promise | (Component | void) 31 | } 32 | 33 | export interface ComponentsDir extends ScanDir { 34 | watch?: boolean 35 | extensions?: string[] 36 | transpile?: 'auto' | boolean 37 | } 38 | 39 | export type componentsDirHook = (dirs: ComponentsDir[]) => void | Promise 40 | export type componentsExtendHook = (components: (ComponentsDir | ScanDir)[]) => void | Promise 41 | 42 | export interface Options { 43 | dirs: (string | ComponentsDir)[] 44 | loader: Boolean 45 | } 46 | 47 | declare module '@nuxt/types/config/index' { 48 | interface NuxtOptions { 49 | components: boolean | Options | Options['dirs'] 50 | } 51 | } 52 | 53 | declare module '@nuxt/types/config/hooks' { 54 | interface NuxtOptionsHooks { 55 | 'components:dirs'?: componentsDirHook 56 | 'components:extend'?: componentsExtendHook 57 | components?: { 58 | dirs?: componentsDirHook 59 | extend?: componentsExtendHook 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /templates/components/index.js: -------------------------------------------------------------------------------- 1 | <%= options.getComponents().map(c => { 2 | const magicComments = [ 3 | `webpackChunkName: "${c.chunkName}"`, 4 | c.prefetch === true || typeof c.prefetch === 'number' ? `webpackPrefetch: ${c.prefetch}` : false, 5 | c.preload === true || typeof c.preload === 'number' ? `webpackPreload: ${c.preload}` : false, 6 | ].filter(Boolean).join(', ') 7 | if (c.isAsync === true || (!isDev /* prod fallback */ && c.isAsync === null)) { 8 | const exp = c.export === 'default' ? `c.default || c` : `c['${c.export}']` 9 | const asyncImport = `() => import('../${relativeToBuild(c.filePath)}' /* ${magicComments} */).then(c => wrapFunctional(${exp}))` 10 | return `export const ${c.pascalName} = ${asyncImport}` 11 | } else { 12 | const exp = c.export === 'default' ? `default as ${c.pascalName}` : c.pascalName 13 | return `export { ${exp} } from '../${relativeToBuild(c.filePath)}'` 14 | } 15 | }).join('\n') %> 16 | 17 | // nuxt/nuxt.js#8607 18 | function wrapFunctional(options) { 19 | if (!options || !options.functional) { 20 | return options 21 | } 22 | 23 | const propKeys = Array.isArray(options.props) ? options.props : Object.keys(options.props || {}) 24 | 25 | return { 26 | render(h) { 27 | const attrs = {} 28 | const props = {} 29 | 30 | for (const key in this.$attrs) { 31 | if (propKeys.includes(key)) { 32 | props[key] = this.$attrs[key] 33 | } else { 34 | attrs[key] = this.$attrs[key] 35 | } 36 | } 37 | 38 | return h(options, { 39 | on: this.$listeners, 40 | attrs, 41 | props, 42 | scopedSlots: this.$scopedSlots, 43 | }, this.$slots.default) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nuxt/components", 3 | "version": "2.2.1", 4 | "description": "Auto Import Components for Nuxt.js", 5 | "repository": "nuxt/components", 6 | "license": "MIT", 7 | "exports": { 8 | ".": "./dist/index.js", 9 | "./*": "./*", 10 | "./package.json": "./package.json", 11 | "./loader": "./dist/loader.js" 12 | }, 13 | "main": "dist/index.js", 14 | "types": "dist/index.d.ts", 15 | "files": [ 16 | "dist", 17 | "lib", 18 | "templates" 19 | ], 20 | "scripts": { 21 | "build": "siroc build", 22 | "dev": "nuxt dev test/fixture", 23 | "lint": "eslint --ext .ts,.js,.vue .", 24 | "prepare": "yarn link && yarn link @nuxt/components", 25 | "prepublishOnly": "yarn build", 26 | "release": "yarn test && standard-version && git push --follow-tags && npm publish", 27 | "test": "yarn lint && jest --verbose" 28 | }, 29 | "dependencies": { 30 | "chalk": "^4.1.2", 31 | "chokidar": "^3.5.2", 32 | "glob": "^7.1.7", 33 | "globby": "^11.0.4", 34 | "scule": "^0.2.1", 35 | "semver": "^7.3.5", 36 | "upath": "^2.0.1", 37 | "vue-template-compiler": "^2.6.14" 38 | }, 39 | "devDependencies": { 40 | "@babel/preset-env": "latest", 41 | "@babel/preset-typescript": "latest", 42 | "@nuxt/test-utils": "latest", 43 | "@nuxt/types": "latest", 44 | "@nuxt/typescript-build": "latest", 45 | "@nuxt/typescript-runtime": "latest", 46 | "@nuxtjs/eslint-config-typescript": "latest", 47 | "@types/jest": "latest", 48 | "@types/loader-utils": "latest", 49 | "@types/lodash": "latest", 50 | "@types/semver": "latest", 51 | "consola": "latest", 52 | "eslint": "latest", 53 | "jest": "latest", 54 | "loader-utils": "latest", 55 | "nuxt-edge": "latest", 56 | "pug": "latest", 57 | "pug-plain-loader": "latest", 58 | "siroc": "0.15.0", 59 | "standard-version": "latest" 60 | }, 61 | "peerDependencies": { 62 | "consola": "*" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /test/unit/loader.test.ts: -------------------------------------------------------------------------------- 1 | import path from 'upath' 2 | import { loader as WebpackLoader } from 'webpack' 3 | import loader from '../../src/loader' 4 | import { scanFixtureComponents } from './utils' 5 | 6 | let testLoader 7 | 8 | beforeAll(async () => { 9 | const fixtureComponents = await scanFixtureComponents() 10 | testLoader = async (context: object, content?: string | Buffer): Promise<{ content: typeof content }> => { 11 | let finalContent: typeof content 12 | 13 | await loader.call({ 14 | addDependency: (_file) => {}, 15 | async: () => {}, 16 | cacheable: (_bool) => {}, 17 | callback: (_, newContent) => { finalContent = newContent }, 18 | query: { 19 | dependencies: [], 20 | getComponents: () => fixtureComponents 21 | }, 22 | ...context 23 | } as WebpackLoader.LoaderContext, content) 24 | 25 | return { content: finalContent } 26 | } 27 | }) 28 | 29 | function expectToContainImports (content: string) { 30 | const fixturePath = p => path.resolve('test/fixture', p).replace(/\\/g, '\\\\') 31 | expect(content).toContain(`require('${fixturePath('components/Foo.vue')}')`) 32 | expect(content).toContain(`require('${fixturePath('components/0-base/1.Button.vue')}')`) 33 | expect(content).toContain(`require('${fixturePath('components/icons/Home.vue')}')`) 34 | } 35 | 36 | test('default', async () => { 37 | const { content } = await testLoader({ resourcePath: path.resolve('test/fixture/pages/index.vue') }, 'test') 38 | expectToContainImports(content) 39 | }) 40 | 41 | test('hot reload', async () => { 42 | const { content } = await testLoader({ resourcePath: path.resolve('test/fixture/pages/index.vue') }, '/* hot reload */') 43 | expectToContainImports(content) 44 | }) 45 | 46 | test('resourceQuery is truthy', async () => { 47 | const { content } = await testLoader({ resourceQuery: 'something' }, 'test') 48 | expect(content).toEqual('test') 49 | }) 50 | 51 | test('no matched components', async () => { 52 | const { content } = await testLoader({ resourcePath: path.resolve('test/fixture/pages/no-components.vue') }, 'test') 53 | expect(content).toEqual('test') 54 | }) 55 | -------------------------------------------------------------------------------- /test/module.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck nuxt internals not typed! 2 | import { setupTest, getNuxt, getContext } from '@nuxt/test-utils' 3 | 4 | const watchers: any[] = [] 5 | jest.mock('chokidar', () => ({ 6 | watch () { 7 | return { 8 | close: jest.fn(), 9 | on (event, fn) { 10 | watchers.push({ event, fn }) 11 | } 12 | } 13 | } 14 | })) 15 | const callChokidarEvent = (eventName, filename = 'test.js') => Promise.all(watchers.map(w => w.fn(eventName, filename))) 16 | 17 | describe('My test', () => { 18 | console.warn = jest.fn() // eslint-disable-line no-console 19 | const componentsDirsHook = jest.fn() 20 | 21 | setupTest({ 22 | testDir: __dirname, 23 | fixture: 'fixture', 24 | configFile: 'nuxt.config.ts', 25 | build: true, 26 | config: { 27 | dev: true, 28 | hooks: { 29 | 'components:dirs': componentsDirsHook, 30 | 'build:before' (builder) { 31 | jest.spyOn(builder, 'generateRoutesAndFiles') 32 | } 33 | } 34 | } 35 | }) 36 | 37 | test('displays autoImported components', async () => { 38 | const { html } = await getNuxt().server.renderRoute('/') 39 | expect(html).toContain('Foo') 40 | expect(html).toContain('Bar') 41 | expect(html).toContain('Base Button') 42 | expect(html).toContain('Icon Home') 43 | }) 44 | 45 | test('displays overwritten component', async () => { 46 | const nuxt = getNuxt() 47 | const { html } = await nuxt.server.renderRoute('/') 48 | expect(html).toContain('app header') 49 | }) 50 | 51 | test('displays autoImported components in pug template', async () => { 52 | const { html } = await getNuxt().server.renderRoute('/pug') 53 | expect(html).toContain('Foo') 54 | expect(html).toContain('Bar') 55 | expect(html).toContain('Base Button') 56 | expect(html).toContain('Icon Home') 57 | }) 58 | 59 | test('watch: rebuild on add/remove', async () => { 60 | const { builder } = getContext() 61 | builder.generateRoutesAndFiles.mockClear() 62 | await callChokidarEvent('add') 63 | expect(builder.generateRoutesAndFiles).toHaveBeenCalledTimes(1) 64 | await callChokidarEvent('unlink') 65 | expect(builder.generateRoutesAndFiles).toHaveBeenCalledTimes(2) 66 | }) 67 | 68 | test('watch: no rebuild on other events', async () => { 69 | const { builder } = getContext() 70 | builder.generateRoutesAndFiles.mockClear() 71 | await callChokidarEvent('foo') 72 | expect(builder.generateRoutesAndFiles).not.toHaveBeenCalled() 73 | }) 74 | 75 | test('hook: components:dirs hook is called', () => { 76 | expect(componentsDirsHook).toHaveBeenCalled() 77 | }) 78 | }) 79 | -------------------------------------------------------------------------------- /src/scan.ts: -------------------------------------------------------------------------------- 1 | import { basename, extname, join, dirname, relative } from 'upath' 2 | import globby from 'globby' 3 | import { pascalCase, splitByCase } from 'scule' 4 | import type { ScanDir, Component } from './types' 5 | 6 | export function sortDirsByPathLength ({ path: pathA }: ScanDir, { path: pathB }: ScanDir): number { 7 | return pathB.split(/[\\/]/).filter(Boolean).length - pathA.split(/[\\/]/).filter(Boolean).length 8 | } 9 | 10 | // vue@2 src/shared/util.js 11 | function hyphenate (str: string):string { 12 | return str.replace(/\B([A-Z])/g, '-$1').toLowerCase() 13 | } 14 | 15 | export async function scanComponents (dirs: ScanDir[], srcDir: string): Promise { 16 | const components: Component[] = [] 17 | const filePaths = new Set() 18 | const scannedPaths: string[] = [] 19 | 20 | for (const { path, pattern, ignore = [], prefix, extendComponent, pathPrefix, level, prefetch = false, preload = false, isAsync: dirIsAsync } of dirs.sort(sortDirsByPathLength)) { 21 | const resolvedNames = new Map() 22 | 23 | for (const _file of await globby(pattern!, { cwd: path, ignore })) { 24 | const filePath = join(path, _file) 25 | 26 | if (scannedPaths.find(d => filePath.startsWith(d))) { 27 | continue 28 | } 29 | 30 | if (filePaths.has(filePath)) { continue } 31 | filePaths.add(filePath) 32 | 33 | // Resolve componentName 34 | const prefixParts = ([] as string[]).concat( 35 | prefix ? splitByCase(prefix) : [], 36 | (pathPrefix !== false) ? splitByCase(relative(path, dirname(filePath))) : [] 37 | ) 38 | let fileName = basename(filePath, extname(filePath)) 39 | if (fileName.toLowerCase() === 'index') { 40 | fileName = pathPrefix === false ? basename(dirname(filePath)) : '' /* inherits from path */ 41 | } 42 | const isAsync = (fileName.endsWith('.async') ? true : dirIsAsync) || null 43 | fileName = fileName.replace(/\.async$/, '') 44 | const fileNameParts = splitByCase(fileName) 45 | 46 | const componentNameParts: string[] = [] 47 | 48 | while (prefixParts.length && 49 | (prefixParts[0] || '').toLowerCase() !== (fileNameParts[0] || '').toLowerCase() 50 | ) { 51 | componentNameParts.push(prefixParts.shift()!) 52 | } 53 | 54 | const componentName = pascalCase(componentNameParts).replace(/^\d+/, '') + 55 | pascalCase(fileNameParts).replace(/^\d+/, '') 56 | 57 | if (resolvedNames.has(componentName)) { 58 | // eslint-disable-next-line no-console 59 | console.warn(`Two component files resolving to the same name \`${componentName}\`:\n` + 60 | `\n - ${filePath}` + 61 | `\n - ${resolvedNames.get(componentName)}` 62 | ) 63 | continue 64 | } 65 | resolvedNames.set(componentName, filePath) 66 | 67 | const pascalName = pascalCase(componentName) 68 | const kebabName = hyphenate(componentName) 69 | const shortPath = relative(srcDir, filePath) 70 | const chunkName = 'components/' + kebabName 71 | 72 | let component: Component = { 73 | filePath, 74 | pascalName, 75 | kebabName, 76 | chunkName, 77 | shortPath, 78 | isAsync, 79 | import: '', 80 | asyncImport: '', 81 | export: 'default', 82 | global: Boolean(global), 83 | level: Number(level), 84 | prefetch: Boolean(prefetch), 85 | preload: Boolean(preload) 86 | } 87 | 88 | if (typeof extendComponent === 'function') { 89 | component = (await extendComponent(component)) || component 90 | } 91 | 92 | component.import = component.import || `require('${component.filePath}').${component.export}` 93 | component.asyncImport = component.asyncImport || `function () { return import('${component.filePath}' /* webpackChunkName: "${component.chunkName}" */).then(function(m) { return m['${component.export}'] || m }) }` 94 | 95 | // Check if component is already defined, used to overwite if level is inferiour 96 | const definedComponent = components.find(c => c.pascalName === component.pascalName) 97 | if (definedComponent && component.level < definedComponent.level) { 98 | Object.assign(definedComponent, component) 99 | } else if (!definedComponent) { 100 | components.push(component) 101 | } 102 | } 103 | 104 | scannedPaths.push(path) 105 | } 106 | 107 | return components 108 | } 109 | 110 | export function matcher (tags: string[], components: Component[]) { 111 | return tags.reduce((matches, tag) => { 112 | const match = components.find(({ pascalName, kebabName }) => [pascalName, kebabName].includes(tag)) 113 | match && matches.push(match) 114 | return matches 115 | }, [] as Component[]) 116 | } 117 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'upath' 3 | import chokidar from 'chokidar' 4 | import type { Configuration as WebpackConfig, Entry as WebpackEntry } from 'webpack' 5 | import type { Module } from '@nuxt/types/config' 6 | import consola from 'consola' 7 | 8 | import { requireNuxtVersion } from './compatibility' 9 | import { scanComponents } from './scan' 10 | import type { Options, ComponentsDir } from './types' 11 | 12 | const isPureObjectOrString = (val: any) => (!Array.isArray(val) && typeof val === 'object') || typeof val === 'string' 13 | const getDir = (p: string) => fs.statSync(p).isDirectory() ? p : path.dirname(p) 14 | 15 | const componentsModule: Module = function () { 16 | const { nuxt } = this 17 | const { components } = nuxt.options 18 | 19 | /* istanbul ignore if */ 20 | if (!components) { 21 | return 22 | } 23 | 24 | requireNuxtVersion(nuxt?.constructor?.version, '2.10') 25 | 26 | const options: Options = { 27 | dirs: ['~/components'], 28 | loader: !nuxt.options.dev, 29 | ...Array.isArray(components) ? { dirs: components } : components 30 | } 31 | 32 | nuxt.hook('build:before', async (builder: any) => { 33 | const nuxtIgnorePatterns: string[] = builder.ignore.ignore ? builder.ignore.ignore._rules.map((rule: any) => rule.pattern) : /* istanbul ignore next */ [] 34 | 35 | await nuxt.callHook('components:dirs', options.dirs) 36 | 37 | const resolvePath = (dir: any) => nuxt.resolver.resolvePath(dir) 38 | 39 | // Add components/global/ directory (backward compatibility to remove prefix) 40 | try { 41 | const globalDir = getDir(resolvePath('~/components/global')) 42 | if (!options.dirs.find(dir => resolvePath(dir) === globalDir)) { 43 | options.dirs.push({ 44 | path: globalDir 45 | }) 46 | } 47 | } catch (err) { 48 | /* istanbul ignore next */ 49 | nuxt.options.watch.push(path.resolve(nuxt.options.srcDir, 'components', 'global')) 50 | } 51 | 52 | const componentDirs = options.dirs.filter(isPureObjectOrString).map((dir) => { 53 | const dirOptions: ComponentsDir = typeof dir === 'object' ? dir : { path: dir } 54 | 55 | let dirPath = dirOptions.path 56 | try { dirPath = getDir(nuxt.resolver.resolvePath(dirOptions.path)) } catch (err) {} 57 | 58 | const transpile = typeof dirOptions.transpile === 'boolean' ? dirOptions.transpile : 'auto' 59 | 60 | // Normalize level 61 | dirOptions.level = Number(dirOptions.level || 0) 62 | 63 | const enabled = fs.existsSync(dirPath) 64 | if (!enabled && dirOptions.path !== '~/components') { 65 | // eslint-disable-next-line no-console 66 | console.warn('Components directory not found: `' + dirPath + '`') 67 | } 68 | 69 | const extensions = dirOptions.extensions || builder.supportedExtensions 70 | 71 | return { 72 | ...dirOptions, 73 | enabled, 74 | path: dirPath, 75 | extensions, 76 | pattern: dirOptions.pattern || `**/*.{${extensions.join(',')},}`, 77 | isAsync: dirOptions.isAsync, 78 | // TODO: keep test/unit/utils.ts updated 79 | ignore: [ 80 | '**/*.stories.{js,ts,jsx,tsx}', // ignore storybook files 81 | '**/*{M,.m,-m}ixin.{js,ts,jsx,tsx}', // ignore mixins 82 | '**/*.d.ts', // .d.ts files 83 | ...nuxtIgnorePatterns, 84 | ...(dirOptions.ignore || []) 85 | ], 86 | transpile: (transpile === 'auto' ? dirPath.includes('node_modules') : transpile) 87 | } 88 | }).filter(d => d.enabled) 89 | 90 | nuxt.options.build!.transpile!.push(...componentDirs.filter(dir => dir.transpile).map(dir => dir.path)) 91 | 92 | let components = await scanComponents(componentDirs, nuxt.options.srcDir!) 93 | await nuxt.callHook('components:extend', components) 94 | 95 | // Add loader for tree shaking in production only 96 | if (options.loader) { 97 | // eslint-disable-next-line no-console 98 | consola.info('Using components loader to optimize imports') 99 | this.extendBuild((config) => { 100 | const vueRule = config.module?.rules.find(rule => rule.test?.toString().includes('.vue')) 101 | if (!vueRule) { 102 | throw new Error('Cannot find vue loader') 103 | } 104 | if (!vueRule.use) { 105 | vueRule.use = [{ 106 | loader: vueRule.loader!.toString(), 107 | options: vueRule.options 108 | }] 109 | delete vueRule.loader 110 | delete vueRule.options 111 | } 112 | if (!Array.isArray(vueRule!.use)) { 113 | // @ts-ignore 114 | vueRule.use = [vueRule.use] 115 | } 116 | 117 | // @ts-ignore 118 | vueRule!.use!.unshift({ 119 | loader: require.resolve('./loader'), 120 | options: { 121 | getComponents: () => components 122 | } 123 | }) 124 | }) 125 | 126 | // Add Webpack entry for runtime installComponents function 127 | nuxt.hook('webpack:config', (configs: WebpackConfig[]) => { 128 | for (const config of configs.filter(c => ['client', 'modern', 'server'].includes(c.name!))) { 129 | ((config.entry as WebpackEntry).app as string[]).unshift(path.resolve(__dirname, '../lib/installComponents.js')) 130 | } 131 | }) 132 | } 133 | 134 | // Watch 135 | // istanbul ignore else 136 | if (nuxt.options.dev && componentDirs.some(dir => dir.watch !== false)) { 137 | const watcher = chokidar.watch(componentDirs.filter(dir => dir.watch !== false).map(dir => dir.path), nuxt.options.watchers!.chokidar) 138 | watcher.on('all', async (eventName) => { 139 | if (!['add', 'unlink'].includes(eventName)) { 140 | return 141 | } 142 | 143 | components = await scanComponents(componentDirs, nuxt.options.srcDir!) 144 | await nuxt.callHook('components:extend', components) 145 | 146 | await builder.generateRoutesAndFiles() 147 | }) 148 | 149 | // Close watcher on nuxt close 150 | nuxt.hook('close', () => { 151 | watcher.close() 152 | }) 153 | } 154 | 155 | // Global components 156 | 157 | // Add templates 158 | const getComponents = () => components 159 | const templates = [ 160 | 'components/index.js', 161 | 'components/plugin.js', 162 | 'components/readme_md', 163 | 'vetur/tags.json' 164 | ] 165 | for (const t of templates) { 166 | this[t.includes('plugin') ? 'addPlugin' : 'addTemplate']({ 167 | src: path.resolve(__dirname, '../templates', t), 168 | fileName: t.replace('_', '.'), 169 | options: { getComponents } 170 | }) 171 | } 172 | 173 | // Add CLI info to inspect discovered components 174 | const componentsListFile = path.resolve(nuxt.options.buildDir, 'components/readme.md') 175 | // eslint-disable-next-line no-console 176 | consola.info('Discovered Components:', path.relative(process.cwd(), componentsListFile)) 177 | }) 178 | } 179 | 180 | // @ts-ignore 181 | componentsModule.meta = { name: '@nuxt/components' } 182 | 183 | export default componentsModule 184 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![@nuxt/components](https://user-images.githubusercontent.com/904724/99790294-2f75d300-2b24-11eb-8114-0a2569913fae.png) 2 | 3 | # @nuxt/components 4 | 5 | [![npm version][npm-version-src]][npm-version-href] 6 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 7 | [![Github Actions CI][github-actions-ci-src]][github-actions-ci-href] 8 | [![Codecov][codecov-src]][codecov-href] 9 | [![License][license-src]][license-href] 10 | 11 | > Module to scan and auto import components for Nuxt 2.13+ 12 | 13 | - [🎲  Play on CodeSandbox](https://githubbox.com/nuxt/components/tree/main/example) 14 | - [🎬  Demonstration video (49s)](https://www.youtube.com/watch?v=lQ8OBrgVVr8) 15 | - [📖  Release Notes](./CHANGELOG.md) 16 | 17 | ## Table of Contents 18 | 19 | - [Features](#features) 20 | - [Usage](#usage) 21 | - [Lazy Imports](#lazy-imports) 22 | - [Overwriting Components](#overwriting-components) 23 | - [Directories](#directories) 24 | - [Directory Properties](#directory-properties) 25 | - [Library authors](#library-authors) 26 | - [License](#license) 27 | 28 | ## Features 29 | 30 | - Automatically scan `components/` directory 31 | - No need to manually import components anymore 32 | - Multiple paths with customizable prefixes and lookup/ignore patterns 33 | - Lazy loading (Async components) 34 | - Production code-splitting optimization (loader) 35 | - Hot reloading 36 | - Module integration ([library authors](#library-authors)) 37 | - Fully tested 38 | 39 | ## Usage 40 | 41 | Set the `components` option in `nuxt.config`: 42 | 43 | ```js 44 | export default { 45 | components: true 46 | } 47 | ``` 48 | 49 | **Note:** If using nuxt `2.10...2.13`, you have to also manually install and add `@nuxt/components` to `buildModules` inside `nuxt.config`. 50 | 51 | **Create your components:** 52 | 53 | ```bash 54 | | components/ 55 | ---| ComponentFoo.vue 56 | ---| ComponentBar.vue 57 | ``` 58 | 59 | **Use them whenever you want, they will be auto imported in `.vue` files :** 60 | 61 | ```html 62 | 66 | ``` 67 | 68 | No need anymore to manually import them in the `script` section! 69 | 70 | See [live demo](https://codesandbox.io/s/nuxt-components-cou9k) or [video example](https://www.youtube.com/watch?v=lQ8OBrgVVr8). 71 | 72 | ### Lazy Imports 73 | 74 | Nuxt by default does code-splitting per page and components. But sometimes we also need to lazy load them: 75 | 76 | - Component size is rather big (or has big dependencies imported) like a text-editor 77 | - Component is rendered conditionally with `v-if` or being in a modal 78 | 79 | In order to [lazy load](https://webpack.js.org/guides/lazy-loading/) a component, all we need to do is to add `Lazy` prefix to component name. 80 | 81 | You now can easily import a component on-demand: 82 | 83 | ```html 84 | 90 | 91 | 105 | ``` 106 | 107 | If you want to prefetch or preload the components with `Lazy` prefix, you can configure it by [prefetch/preload options](#prefetch/preload). 108 | 109 | ### Nested Components 110 | 111 | If you have components in nested directories: 112 | 113 | ```bash 114 | | components/ 115 | ---| my/ 116 | ------| form/ 117 | ---------| TextArea.vue 118 | ``` 119 | 120 | The component name will contain its path: 121 | 122 | ```html 123 | 124 | ``` 125 | 126 | For clarity, it is recommended that component file name matches its name. You can also use `MyFormTextArea.vue` as name with same directory structure. 127 | 128 | If for any reason different prefix is desired, we can add specific directory with the `prefix` option: (See [directories](#directories) section) 129 | 130 | ```js 131 | components: ['~/components/', { path: '~/components/foo/', prefix: 'foo' }] 132 | ``` 133 | 134 | ## Overwriting Components 135 | 136 | It is possible to have a way to overwrite components using the [level](#level) option. This is very useful for modules and theme authors. 137 | 138 | Considering this structure: 139 | 140 | ```bash 141 | | node_modules/ 142 | ---| my-theme/ 143 | ------| components/ 144 | ---------| Header.vue 145 | | components/ 146 | ---| Header.vue 147 | ``` 148 | 149 | Then defining in the `nuxt.config`: 150 | 151 | ```js 152 | components: [ 153 | '~/components', // default level is 0 154 | { path: 'node_modules/my-theme/components', level: 1 } 155 | ] 156 | ``` 157 | 158 | Our `components/Header.vue` will overwrite our theme component since the lowest level overwrites. 159 | 160 | ## Directories 161 | 162 | By setting `components: true`, default `~/components` directory will be included. 163 | However you can customize module behaviour by providing directories to scan: 164 | 165 | ```js 166 | export default { 167 | components: [ 168 | '~/components', // shortcut to { path: '~/components' } 169 | { path: '~/components/awesome/', prefix: 'awesome' } 170 | ] 171 | } 172 | ``` 173 | 174 | Each item can be either string or object. String is shortcut to `{ path }`. 175 | 176 | **Note:** Don't worry about ordering or overlapping directories! Components module will take care of it. Each file will be only matched once with longest path. 177 | 178 | ### Directory Properties 179 | 180 | #### path 181 | 182 | - Required 183 | - Type: `String` 184 | 185 | Path (absolute or relative) to the directory containing your components. 186 | 187 | You can use Nuxt aliases (`~` or `@`) to refer to directories inside project or directly use a npm package path similar to require. 188 | 189 | #### extensions 190 | 191 | - Type: `Array` 192 | - Default: 193 | - Extensions supported by Nuxt builder (`builder.supportedExtensions`) 194 | - Default supported extensions `['vue', 'js']` or `['vue', 'js', 'ts', 'tsx']` depending on your environment 195 | 196 | **Example:** Support multi-file component structure 197 | 198 | If you prefer to split your SFCs into `.js`, `.vue` and `.css`, you can only enable `.vue` files to be scanned: 199 | 200 | ``` 201 | | components 202 | ---| componentC 203 | ------| componentC.vue 204 | ------| componentC.js 205 | ------| componentC.scss 206 | ``` 207 | 208 | ```js 209 | // nuxt.config.js 210 | export default { 211 | components: [{ path: '~/components', extensions: ['vue'] }] 212 | } 213 | ``` 214 | 215 | #### pattern 216 | 217 | - Type: `string` ([glob pattern](https://github.com/isaacs/node-glob#glob-primer)) 218 | - Default: `**/*.${extensions.join(',')}` 219 | 220 | Accept Pattern that will be run against specified `path`. 221 | 222 | #### ignore 223 | 224 | - Type: `Array` 225 | - Items: `string` ([glob pattern](https://github.com/isaacs/node-glob#glob-primer)) 226 | - Default: `[]` 227 | 228 | Ignore patterns that will be run against specified `path`. 229 | 230 | #### prefix 231 | 232 | - Type: `String` 233 | - Default: `''` (no prefix) 234 | 235 | Prefix all matched components. 236 | 237 | Example below adds `awesome-`/`Awesome` prefix to the name of components in `awesome/` directory. 238 | 239 | ```js 240 | // nuxt.config.js 241 | export default { 242 | components: [ 243 | '~/components', 244 | { path: '~/components/awesome/', prefix: 'awesome' } 245 | ] 246 | } 247 | ``` 248 | 249 | ```bash 250 | components/ 251 | awesome/ 252 | Button.vue 253 | Button.vue 254 | ``` 255 | 256 | ```html 257 | 263 | ``` 264 | 265 | #### pathPrefix 266 | 267 | - Type: `Boolean` 268 | - Default: `true` 269 | 270 | Prefix component name by it's path 271 | 272 | #### watch 273 | 274 | - Type: `Boolean` 275 | - Default: `true` 276 | 277 | Watch specified `path` for changes, including file additions and file deletions. 278 | 279 | #### transpile 280 | 281 | - Type: `Boolean` 282 | - Default: `'auto'` 283 | 284 | Transpile specified `path` using [`build.transpile`](https://nuxtjs.org/api/configuration-build#transpile), by default (`'auto'`) it will set `transpile: true` if `node_modules/` is in `path`. 285 | 286 | #### level 287 | 288 | - Type: `Number` 289 | - Default: `0` 290 | 291 | Level are use to define a hint when overwriting the components which have the same name in two different directories, this is useful for theming. 292 | 293 | ```js 294 | export default { 295 | components: [ 296 | '~/components', // default level is 0 297 | { path: 'my-theme/components', level: 1 } 298 | ] 299 | } 300 | ``` 301 | 302 | Components having the same name in `~/components` will overwrite the one in `my-theme/components`, learn more in [Overwriting Components](#overwriting-components). The lowest value will overwrite. 303 | 304 | #### prefetch/preload 305 | 306 | - Type: `Boolean/Number` 307 | - Default: `false` 308 | 309 | These properties are used in production to configure how [components with `Lazy` prefix](#Lazy-Imports) are handled by Wepack via its magic comments, learn more in [Wepack's official documentation](https://webpack.js.org/api/module-methods/#magic-comments). 310 | 311 | ```js 312 | export default { 313 | components: [{ path: 'my-theme/components', prefetch: true }] 314 | } 315 | ``` 316 | 317 | yields: 318 | 319 | ```js 320 | // plugin.js 321 | const componets = { 322 | MyComponentA: import(/* webpackPrefetch: true */ ...), 323 | MyComponentB: import(/* webpackPrefetch: true */ ...) 324 | } 325 | ``` 326 | 327 | #### isAsync 328 | 329 | - Type: Boolean 330 | - Default: `false` unless component name ends with `.async.vue` 331 | 332 | This flag indicates, component should be loaded async (with a seperate chunk) regardless of using `Lazy` prefix or not. 333 | 334 | ## Migration guide 335 | 336 | ## `v1` to `v2` 337 | 338 | Starting with `nuxt@2.15`, Nuxt uses `@nuxt/components` v2: 339 | 340 | - All components are globally available so you can move `components/global/` 341 | to `components/` and `global: true` is not required anymore 342 | - Full path inside `components` is used to prefix component names. If you were structing your 343 | components in multiple directories, should either add prefix or register in `components` section of `nuxt.config` or use new `pathPrefix` option. 344 | 345 | **Example:** 346 | 347 | ``` 348 | components 349 | ├── atoms 350 | │ └── icons 351 | ├── molecules 352 | │ └── illustrations 353 | ├── organisms 354 | │ └── ads 355 | └── templates 356 | ├── blog 357 | └── home 358 | ``` 359 | 360 | ```js 361 | // nuxt.config.js 362 | export default { 363 | components: [ 364 | '~/components/templates', 365 | '~/components/atoms', 366 | '~/components/molecules', 367 | '~/components/organisms' 368 | ] 369 | } 370 | ``` 371 | 372 | ## Library Authors 373 | 374 | Making Vue Component libraries with automatic tree-shaking and component registration is now damn easy ✨ 375 | 376 | This module expose a hook named `components:dirs` so you can easily extend the directory list without updating user configuration in your Nuxt module. 377 | 378 | Imagine a directory structure like this: 379 | 380 | ```bash 381 | | node_modules/ 382 | ---| awesome-ui/ 383 | ------| components/ 384 | ---------| Alert.vue 385 | ---------| Button.vue 386 | ------| nuxt.js 387 | | pages/ 388 | ---| index.vue 389 | | nuxt.config.js 390 | ``` 391 | 392 | Then in `awesome-ui/nuxt.js` you can use the `components:dir` hook: 393 | 394 | ```js 395 | import { join } from 'path' 396 | 397 | export default function() { 398 | this.nuxt.hook('components:dirs', dirs => { 399 | // Add ./components dir to the list 400 | dirs.push({ 401 | path: join(__dirname, 'components'), 402 | prefix: 'awesome' 403 | }) 404 | }) 405 | } 406 | ``` 407 | 408 | That's it! Now in your project, you can import your ui library as a Nuxt module in your `nuxt.config.js`: 409 | 410 | ```js 411 | export default { 412 | buildModules: ['@nuxt/components', 'awesome-ui/nuxt'] 413 | } 414 | ``` 415 | 416 | And directly use the module components (prefixed with `awesome-`), our `pages/index.vue`: 417 | 418 | ```vue 419 | 425 | ``` 426 | 427 | It will automatically import the components only if used and also support HMR when updating your components in `node_modules/awesome-ui/components/`. 428 | 429 | Next: publish your `awesome-ui` module to [npm](https://www.npmjs.com) and share it with the other Nuxters ✨ 430 | 431 | ## License 432 | 433 | [MIT](./LICENSE) 434 | 435 | 436 | 437 | [npm-version-src]: https://img.shields.io/npm/v/@nuxt/components/latest.svg?style=flat-square 438 | [npm-version-href]: https://npmjs.com/package/@nuxt/components 439 | [npm-downloads-src]: https://img.shields.io/npm/dt/@nuxt/components.svg?style=flat-square 440 | [npm-downloads-href]: https://npmjs.com/package/@nuxt/components 441 | [github-actions-ci-src]: https://img.shields.io/github/workflow/status/nuxt/typescript/test?label=ci&style=flat-square 442 | [github-actions-ci-href]: https://github.com/nuxt/components/actions?query=workflow%3Aci 443 | [codecov-src]: https://img.shields.io/codecov/c/github/nuxt/components.svg?style=flat-square 444 | [codecov-href]: https://codecov.io/gh/nuxt/components 445 | [license-src]: https://img.shields.io/npm/l/@nuxt/components.svg?style=flat-square 446 | [license-href]: https://npmjs.com/package/@nuxt/components 447 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [2.2.1](https://github.com/nuxt/components/compare/v2.2.0...v2.2.1) (2021-08-16) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * async import template (resolves [#223](https://github.com/nuxt/components/issues/223), resolves [#225](https://github.com/nuxt/components/issues/225)) ([0fec884](https://github.com/nuxt/components/commit/0fec88484399fbd41251f4bcb44d17a9cae04338)) 11 | * set default for `isAsync` in template ([c36fd14](https://github.com/nuxt/components/commit/c36fd14902f64af1abdb683148355b225dbd0874)) 12 | * use loader for non async components only ([c39a593](https://github.com/nuxt/components/commit/c39a593e573002b07a19eae032121d4235d22a1e)) 13 | 14 | ## [2.2.0](https://github.com/nuxt/components/compare/v2.2.0-0...v2.2.0) (2021-08-11) 15 | 16 | ## [2.2.0-0](https://github.com/nuxt/components/compare/v2.1.8...v2.2.0-0) (2021-08-11) 17 | 18 | 19 | ### Features 20 | 21 | * explicit async loading ([#212](https://github.com/nuxt/components/issues/212)) ([5adefb9](https://github.com/nuxt/components/commit/5adefb9b90d939922c5bc4055f977d44a879e0f0)) 22 | * remove numeric prefix from component names ([#222](https://github.com/nuxt/components/issues/222)) ([a615dd1](https://github.com/nuxt/components/commit/a615dd1ae4f5fbb6dac330f78c5ea79f63582379)) 23 | * support webpack magic comments ([#197](https://github.com/nuxt/components/issues/197)) ([4e88e8f](https://github.com/nuxt/components/commit/4e88e8f04d2af7464d8f1d40548a8c1adf013697)) 24 | * **types:** export hook types ([#208](https://github.com/nuxt/components/issues/208)) ([64e10cc](https://github.com/nuxt/components/commit/64e10cc614b701467937031a0ac248af457b957b)) 25 | 26 | 27 | ### Bug Fixes 28 | 29 | * **types:** make prefetch/preload optional ([#207](https://github.com/nuxt/components/issues/207)) ([48b9247](https://github.com/nuxt/components/commit/48b9247bada81cf4bbafc9516aa959cce06aee0d)) 30 | 31 | ### [2.1.8](https://github.com/nuxt/components/compare/v2.1.7...v2.1.8) (2021-04-28) 32 | 33 | 34 | ### Bug Fixes 35 | 36 | * **pkg:** explicitly export package.json (resolves [#191](https://github.com/nuxt/components/issues/191)) ([5949790](https://github.com/nuxt/components/commit/59497903614ff1a47c190bac5115b0622190831a)) 37 | 38 | ### [2.1.7](https://github.com/nuxt/components/compare/v2.1.6...v2.1.7) (2021-04-28) 39 | 40 | 41 | ### Bug Fixes 42 | 43 | * use consola for output info ([#184](https://github.com/nuxt/components/issues/184)) ([c6c3f66](https://github.com/nuxt/components/commit/c6c3f66f0bb86c3d7a2a1eac737aa145e64b0fc7)) 44 | 45 | ### [2.1.6](https://github.com/nuxt/components/compare/v2.1.5...v2.1.6) (2021-04-06) 46 | 47 | 48 | ### Bug Fixes 49 | 50 | * use parent dir name for index.[ext] components without pathPrefix ([#178](https://github.com/nuxt/components/issues/178)) ([6ba6d22](https://github.com/nuxt/components/commit/6ba6d2243a0ebac2acc515ded3003827f62f7505)) 51 | 52 | ### [2.1.5](https://github.com/nuxt/components/compare/v2.1.4...v2.1.5) (2021-04-06) 53 | 54 | 55 | ### Bug Fixes 56 | 57 | * avoid importing global entry (fixes [#176](https://github.com/nuxt/components/issues/176)) ([c821719](https://github.com/nuxt/components/commit/c821719a2b47a57b0252426975992ffd8143a939)) 58 | 59 | ### [2.1.4](https://github.com/nuxt/components/compare/v2.1.3...v2.1.4) (2021-03-23) 60 | 61 | 62 | ### Bug Fixes 63 | 64 | * use wrapper for lazy-loaded functional components ([#172](https://github.com/nuxt/components/issues/172)) ([0210066](https://github.com/nuxt/components/commit/021006680e7d37f0f4aaa1bfe7de8a4692f2eb9e)) 65 | 66 | ### [2.1.3](https://github.com/nuxt/components/compare/v2.1.2...v2.1.3) (2021-03-11) 67 | 68 | 69 | ### Bug Fixes 70 | 71 | * use hyphenate instead of kebabCase (fixes [#165](https://github.com/nuxt/components/issues/165)) ([3de27ff](https://github.com/nuxt/components/commit/3de27ff47cbe6e7c712a2a105c257dac1ca64674)) 72 | 73 | ### [2.1.2](https://github.com/nuxt/components/compare/v2.1.1...v2.1.2) (2021-02-22) 74 | 75 | 76 | ### Bug Fixes 77 | 78 | * avoid using .md for templates (resolves nuxt/nuxt.js[#8860](https://github.com/nuxt/components/issues/8860)) ([9e6e180](https://github.com/nuxt/components/commit/9e6e1808c09ec78004715ffb009055b0dda11986)) 79 | 80 | ### [2.1.1](https://github.com/nuxt/components/compare/v2.1.0...v2.1.1) (2021-02-17) 81 | 82 | 83 | ### Bug Fixes 84 | 85 | * **pkg:** change postinstall to prepare ([6742b4a](https://github.com/nuxt/components/commit/6742b4ac1fd734e6199ce82d4eaf6d40dbd8f4d3)) 86 | 87 | ## [2.1.0](https://github.com/nuxt/components/compare/v2.0.0...v2.1.0) (2021-02-16) 88 | 89 | 90 | ### Features 91 | 92 | * allow disabling prefix by path using pathPrefix option ([#150](https://github.com/nuxt/components/issues/150)) ([2a2acdf](https://github.com/nuxt/components/commit/2a2acdf0826d360e7229e0e30b3c844a9c315283)) 93 | 94 | 95 | ### Bug Fixes 96 | 97 | * ignore .d.ts files (resolves [#146](https://github.com/nuxt/components/issues/146)) ([1062abf](https://github.com/nuxt/components/commit/1062abf6264987bbdce6e23a61ce8780f8aab1ba)) 98 | * improve scanner ([#149](https://github.com/nuxt/components/issues/149)) ([62e951c](https://github.com/nuxt/components/commit/62e951cb607336d957b120bd6fdc34ebe91aa089)) 99 | * **scan:** avoid duplicate prefix ([#148](https://github.com/nuxt/components/issues/148)) ([276aa76](https://github.com/nuxt/components/commit/276aa7672c6c4b434c7eb03c6215c4501ef553ba)) 100 | 101 | ## [2.0.0](https://github.com/nuxt/components/compare/v2.0.0-1...v2.0.0) (2021-02-15) 102 | 103 | 104 | ### Bug Fixes 105 | 106 | * register global components with lazy prefix ([ae7249f](https://github.com/nuxt/components/commit/ae7249fd8dcb54b3a4c4f6e231faadc64f32dd9a)), closes [#139](https://github.com/nuxt/components/issues/139) 107 | 108 | ## [2.0.0-1](https://github.com/nuxt/components/compare/v2.0.0-0...v2.0.0-1) (2021-02-13) 109 | 110 | 111 | ### Bug Fixes 112 | 113 | * allow components to be in multilevel nested folders ([#141](https://github.com/nuxt/components/issues/141)) ([9aa52f4](https://github.com/nuxt/components/commit/9aa52f458698ba058e073c59b46460e7d2a46028)) 114 | 115 | ## [2.0.0-0](https://github.com/nuxt/components/compare/v1.2.6...v2.0.0-0) (2021-02-10) 116 | 117 | 118 | ### ⚠ BREAKING CHANGES 119 | 120 | * prefix components in nested dirs with full path (#81) 121 | 122 | ### Features 123 | 124 | * components always available globally (hybrid loader) ([#126](https://github.com/nuxt/components/issues/126)) ([7a855dc](https://github.com/nuxt/components/commit/7a855dcbfb151990e939f95abf6b70fa64ad672b)) 125 | * included pattern to ignore mixins ([#136](https://github.com/nuxt/components/issues/136)) ([aa6da2f](https://github.com/nuxt/components/commit/aa6da2fcf895a62e389dc3df6d62b32c100ba7fb)) 126 | * prefix components in nested dirs with full path ([#81](https://github.com/nuxt/components/issues/81)) ([84a5de7](https://github.com/nuxt/components/commit/84a5de7e68dbb0536e35b656a2b21626adffcfc9)) 127 | 128 | ### [1.2.6](https://github.com/nuxt/components/compare/v1.2.5...v1.2.6) (2021-02-01) 129 | 130 | 131 | ### Bug Fixes 132 | 133 | * **ignore:** ignore typescript stories ([#135](https://github.com/nuxt/components/issues/135)) ([cf54c71](https://github.com/nuxt/components/commit/cf54c71f6efe5a1307772f88f174006f639cbea1)) 134 | 135 | ### [1.2.5](https://github.com/nuxt/components/compare/v1.2.4...v1.2.5) (2021-01-13) 136 | 137 | 138 | ### Bug Fixes 139 | 140 | * **scan:** preserve original component name for webpack chunk ([82357b2](https://github.com/nuxt/components/commit/82357b20651fa792927e6c98bee713a8cf72c619)) 141 | 142 | ### [1.2.4](https://github.com/nuxt/components/compare/v1.2.3...v1.2.4) (2021-01-12) 143 | 144 | 145 | ### Bug Fixes 146 | 147 | * **scan:** generate chunkName based on componentName and use upath ([1b5139a](https://github.com/nuxt/components/commit/1b5139a861712f800f34b1bfed73b59dfd49a404)), closes [nuxt/content#711](https://github.com/nuxt/content/issues/711) 148 | * **types:** fix generated types ([57d6eb5](https://github.com/nuxt/components/commit/57d6eb5f14a9083e755012107e5e8025f2e1fdd0)) 149 | 150 | ### [1.2.3](https://github.com/nuxt/components/compare/v1.2.2...v1.2.3) (2021-01-06) 151 | 152 | 153 | ### Bug Fixes 154 | 155 | * transform to PascalCase only on first lower letter or name contains hyphens ([#129](https://github.com/nuxt/components/issues/129)) ([2c819e6](https://github.com/nuxt/components/commit/2c819e6776ed8142394fc823f8b992e70389e242)) 156 | 157 | ### [1.2.2](https://github.com/nuxt/components/compare/v1.2.1...v1.2.2) (2020-12-07) 158 | 159 | 160 | ### Bug Fixes 161 | 162 | * convert const to var for ie compatibility ([eac269c](https://github.com/nuxt/components/commit/eac269c41905ed937793e2fe2a0432034241c0b8)) 163 | 164 | ### [1.2.1](https://github.com/nuxt/components/compare/v1.2.0...v1.2.1) (2020-12-06) 165 | 166 | 167 | ### Bug Fixes 168 | 169 | * **module:** avoid adding global directory if manually added ([#120](https://github.com/nuxt/components/issues/120)) ([dad04f7](https://github.com/nuxt/components/commit/dad04f7e2971e04619c8369011efe753c048d8df)) 170 | * don't use implicit dependencies ([#119](https://github.com/nuxt/components/issues/119)) ([ceaaaec](https://github.com/nuxt/components/commit/ceaaaec7e36e44aa0b1f097f17f9e66106d64e41)) 171 | * ie compatibility (resolves [#109](https://github.com/nuxt/components/issues/109)) ([04bea9b](https://github.com/nuxt/components/commit/04bea9bc3e44533fc6c6128234a7c13eb3f704ce)) 172 | 173 | ## [1.2.0](https://github.com/nuxt/components/compare/v1.1.1...v1.2.0) (2020-11-23) 174 | 175 | 176 | ### Features 177 | 178 | * add components/global/ directory ([#113](https://github.com/nuxt/components/issues/113)) ([dde86a7](https://github.com/nuxt/components/commit/dde86a7ba72425f771571e12373ffa261e880f53)) 179 | * support overwriting components ([#96](https://github.com/nuxt/components/issues/96)) ([47a21e9](https://github.com/nuxt/components/commit/47a21e9a016c20022e4ec9c6ae3dac469a91a4a1)) 180 | 181 | ### [1.1.1](https://github.com/nuxt/components/compare/v1.1.0...v1.1.1) (2020-10-22) 182 | 183 | 184 | ### Bug Fixes 185 | 186 | * **npm:** component may require wrong webpack version ([3fc657a](https://github.com/nuxt/components/commit/3fc657ab88fe5bfc832e9d0c8c0cd0afe4ee555b)) 187 | 188 | ## [1.1.0](https://github.com/nuxt/components/compare/v1.0.7...v1.1.0) (2020-08-04) 189 | 190 | 191 | ### Features 192 | 193 | * ignore storybook stories ([#84](https://github.com/nuxt/components/issues/84)) ([b4d9e11](https://github.com/nuxt/components/commit/b4d9e113f2967507b2c827f57f4564273f96b6e9)) 194 | 195 | 196 | ### Bug Fixes 197 | 198 | * invalid `webpackChunkName` for global exports ([3fc3709](https://github.com/nuxt/components/commit/3fc3709729310ea3e6d8a3005b8b951045903dfc)) 199 | 200 | ### [1.0.7](https://github.com/nuxt/components/compare/v1.0.6...v1.0.7) (2020-07-05) 201 | 202 | 203 | ### Bug Fixes 204 | 205 | * disable loader for global dirs ([#70](https://github.com/nuxt/components/issues/70)) ([78baa92](https://github.com/nuxt/components/commit/78baa92880c3985e0382a35d5d9b67f7fb968e29)), closes [#18](https://github.com/nuxt/components/issues/18) 206 | 207 | ### [1.0.6](https://github.com/nuxt/components/compare/v1.0.5...v1.0.6) (2020-06-28) 208 | 209 | 210 | ### Bug Fixes 211 | 212 | * **types:** extend NuxtOptions and use 2.13 NuxtOptionsHooks ([dd9ec90](https://github.com/nuxt/components/commit/dd9ec90d20b472fe08f88480d84451f36371fc2e)) 213 | 214 | ### [1.0.5](https://github.com/nuxt/components/compare/v1.0.4...v1.0.5) (2020-06-24) 215 | 216 | 217 | ### Bug Fixes 218 | 219 | * disable module if no components option provided ([#60](https://github.com/nuxt/components/issues/60)) ([8be7b26](https://github.com/nuxt/components/commit/8be7b261f0c05364ef441ef021f93fb39f24ec82)) 220 | 221 | ### [1.0.4](https://github.com/nuxt/components/compare/v1.0.3...v1.0.4) (2020-06-23) 222 | 223 | 224 | ### Bug Fixes 225 | 226 | * ensure requireNuxtVersion skips when currentVersion is nullish ([bf7dbfa](https://github.com/nuxt/components/commit/bf7dbfadd81e261b69e7615cf2cd7a87200e2589)) 227 | 228 | ### [1.0.3](https://github.com/nuxt/components/compare/v1.0.2...v1.0.3) (2020-06-19) 229 | 230 | 231 | ### Bug Fixes 232 | 233 | * **installComponents:** avoid function shorthand for IE support (fixes [#56](https://github.com/nuxt/components/issues/56)) ([201914b](https://github.com/nuxt/components/commit/201914bd71b4b4c7215e0da1d705984991a38b53)) 234 | 235 | ### [1.0.2](https://github.com/nuxt/components/compare/v1.0.1...v1.0.2) (2020-06-19) 236 | 237 | ### [1.0.1](https://github.com/nuxt/components/compare/v1.0.0...v1.0.1) (2020-06-19) 238 | 239 | 240 | ### Bug Fixes 241 | 242 | * **plugin:** add missing wrapper ([9e42435](https://github.com/nuxt/components/commit/9e4243577c0da145f8ee5d17a9519b8fe0d70187)) 243 | 244 | ## [1.0.0](https://github.com/nuxt/components/compare/v0.3.4...v1.0.0) (2020-06-18) 245 | 246 | 247 | ### Bug Fixes 248 | 249 | * add missing comma for multiple global components ([#54](https://github.com/nuxt/components/issues/54)) ([2822a44](https://github.com/nuxt/components/commit/2822a44dfd18483683320f11d4372f85bb4423b8)) 250 | 251 | ### [0.3.5](https://github.com/nuxt/components/compare/v0.3.4...v0.3.5) (2020-06-18) 252 | 253 | ### [0.3.4](https://github.com/nuxt/components/compare/v0.3.3...v0.3.4) (2020-06-12) 254 | 255 | 256 | ### Features 257 | 258 | * support functional components ([#41](https://github.com/nuxt/components/issues/41)) ([5fe2de6](https://github.com/nuxt/components/commit/5fe2de6aa3cb54055e596657c5cffdf1e27f1c2b)) 259 | * support global async components ([#43](https://github.com/nuxt/components/issues/43)) ([6dfef95](https://github.com/nuxt/components/commit/6dfef95fbc818e8047db698acba484944340d253)) 260 | 261 | 262 | ### Bug Fixes 263 | 264 | * disable telemetry for example ([73fe953](https://github.com/nuxt/components/commit/73fe9537c0280344433878642f04a14401630204)) 265 | * provide default value for versions ([16eb18a](https://github.com/nuxt/components/commit/16eb18ab4f30581518be56b30be5beb5c01ac473)) 266 | 267 | ### [0.3.3](https://github.com/nuxt/components/compare/v0.3.2...v0.3.3) (2020-06-01) 268 | 269 | 270 | ### Bug Fixes 271 | 272 | * **build:** add loader entrypoint ([b2275bb](https://github.com/nuxt/components/commit/b2275bb4deb6f777609d5bb08be0bf4614bbc32d)) 273 | 274 | ### [0.3.2](https://github.com/nuxt/components/compare/v0.3.1...v0.3.2) (2020-06-01) 275 | 276 | 277 | ### Features 278 | 279 | * support `dir.extensions` ([#32](https://github.com/nuxt/components/issues/32)) ([0b76ddd](https://github.com/nuxt/components/commit/0b76dddd5513ee9048c73142d8fbafb383d36f6f)) 280 | * use globby ([#33](https://github.com/nuxt/components/issues/33)) ([cdac9b9](https://github.com/nuxt/components/commit/cdac9b9856fad581d3392042c307d3839288edb7)) 281 | 282 | 283 | ### Bug Fixes 284 | 285 | * **scan:** support index.{ext} ([#30](https://github.com/nuxt/components/issues/30)) ([8fcf984](https://github.com/nuxt/components/commit/8fcf984487b7babef1b58e6a5fca172f8e96d5b4)) 286 | 287 | ### [0.3.1](https://github.com/nuxt/components/compare/v0.3.0...v0.3.1) (2020-05-29) 288 | 289 | 290 | ### Features 291 | 292 | * components array and simplify usage ([#27](https://github.com/nuxt/components/issues/27)) ([6fc2ba0](https://github.com/nuxt/components/commit/6fc2ba0ec5b8d672cd722b60e014079904698458)) 293 | 294 | ## [0.3.0](https://github.com/nuxt/components/compare/v0.2.5...v0.3.0) (2020-05-25) 295 | 296 | 297 | ### Features 298 | 299 | * add pug support ([#21](https://github.com/nuxt/components/issues/21)) ([52584dd](https://github.com/nuxt/components/commit/52584dda4ea949e307fbe7880bdc5322e147c453)) 300 | 301 | ### [0.2.5](https://github.com/nuxt/components/compare/v0.2.4...v0.2.5) (2020-05-21) 302 | 303 | 304 | ### Bug Fixes 305 | 306 | * add missing `components:extend` hook call for initial build ([9a29c8b](https://github.com/nuxt/components/commit/9a29c8bdbe505f4a95cc0585817b37bdf92bbba8)) 307 | 308 | ### [0.2.4](https://github.com/nuxt/components/compare/v0.2.3...v0.2.4) (2020-05-20) 309 | 310 | 311 | ### Features 312 | 313 | * `components:extend` hook ([462834f](https://github.com/nuxt/components/commit/462834f7cdd77fadad842367db9bceb7cdad6637)) 314 | * auto generate components.json, global plugin for tests and vetur tags ([#14](https://github.com/nuxt/components/issues/14)) ([12d546d](https://github.com/nuxt/components/commit/12d546d3f98183626693f3b4c0c13aadef7afc93)) 315 | * support `extendComponent` option for scan ([4baa840](https://github.com/nuxt/components/commit/4baa8405800e711bcf58c799f85e3954b0a3c741)) 316 | 317 | 318 | ### Bug Fixes 319 | 320 | * don't override imports if extendComponent already provided ([01959b1](https://github.com/nuxt/components/commit/01959b16c554408fd78c1bf1c2248f8e806e0275)) 321 | * properly resolve components dir name and warn about non existent dirs ([#12](https://github.com/nuxt/components/issues/12)) ([1ffea77](https://github.com/nuxt/components/commit/1ffea77fb3a800a4e14670d651a615c2862a6540)) 322 | 323 | ### [0.2.3](https://github.com/nuxt/components/compare/v0.2.2...v0.2.3) (2020-05-18) 324 | 325 | 326 | ### Features 327 | 328 | * set chunk name for async components ([3cb6aaa](https://github.com/nuxt/components/commit/3cb6aaaec83d9641ebd85b22e6e4bad4607bc33b)) 329 | 330 | 331 | ### Bug Fixes 332 | 333 | * remove moduleOptions to only use components key ([58c7e26](https://github.com/nuxt/components/commit/58c7e26f72576dfbbcaf790f332a73f73090936a)) 334 | 335 | ### [0.2.2](https://github.com/nuxt/components/compare/v0.2.1...v0.2.2) (2020-05-17) 336 | 337 | 338 | ### Bug Fixes 339 | 340 | * **scan:** use unix paths for windows ([9b5e6c6](https://github.com/nuxt/components/commit/9b5e6c6ea6bdd38dd03156428dc9c917165b1b45)) 341 | 342 | ### [0.2.1](https://github.com/nuxt/components/compare/v0.2.0...v0.2.1) (2020-05-13) 343 | 344 | 345 | ### Features 346 | 347 | * add components:dirs hook ([#6](https://github.com/nuxt/components/issues/6)) ([ed83955](https://github.com/nuxt/components/commit/ed8395569e81691147872d5d08aeec61ad3ea756)) 348 | 349 | ## [0.2.0](https://github.com/nuxt/components/compare/v0.1.2...v0.2.0) (2020-04-30) 350 | 351 | 352 | ### Features 353 | 354 | * implements new specs ([#2](https://github.com/nuxt/components/issues/2)) ([f4e5972](https://github.com/nuxt/components/commit/f4e5972188cd14db226b93fca45b7a3f4e36cdc7)) 355 | * throw error if nuxt version not supported ([757af3c](https://github.com/nuxt/components/commit/757af3c7f793d28cfdc22f91af0d0ebccbdde05d)) 356 | 357 | ### [0.1.2](https://github.com/nuxt/components/compare/v0.1.1...v0.1.2) (2020-04-24) 358 | 359 | 360 | ### Bug Fixes 361 | 362 | * fix IE compatibility ([7fa2785](https://github.com/nuxt/components/commit/7fa278578b2bb018f26b31adda25f59860a310b8)) 363 | 364 | ### [0.1.1](https://github.com/nuxt/components/compare/v0.1.0...v0.1.1) (2020-04-23) 365 | 366 | 367 | ### Bug Fixes 368 | 369 | * add webpack entry instead of using nuxt plugin ([eaa4013](https://github.com/nuxt/components/commit/eaa4013541cee918b00c37ea11c7fae9d754fa32)) 370 | 371 | ## [0.1.0](https://github.com/nuxt/components/tree/v0.1.0) (2020-04-22) 372 | -------------------------------------------------------------------------------- /test/fixture/components/global/Big.async.vue: -------------------------------------------------------------------------------- 1 | 6 | --------------------------------------------------------------------------------