diff --git a/README.md b/README.md index 079d7f2..33b3744 100755 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ See [live demo](https://codesandbox.io/s/nuxt-components-cou9k) or [video exampl ### Lazy Imports Nuxt by default does code-splitting per page and components. But sometimes we also need to lazy load them: + - Component size is rather big (or has big dependencies imported) like a text-editor - Component is rendered conditionally with `v-if` or being in a modal @@ -88,18 +89,18 @@ You now can easily import a component on-demand: ``` @@ -127,10 +128,7 @@ For clarity, it is recommended that component file name matches its name. You ca If for any reason different prefix is desired, we can add specific directory with the `prefix` option: (See [directories](#directories) section) ```js -components: [ - '~/components/', - { path: '~/components/foo/', prefix: 'foo' } -] +components: ['~/components/', { path: '~/components/foo/', prefix: 'foo' }] ``` ## Overwriting Components @@ -169,7 +167,7 @@ export default { components: [ '~/components', // shortcut to { path: '~/components' } { path: '~/components/awesome/', prefix: 'awesome' } - ], + ] } ``` @@ -210,15 +208,13 @@ If you prefer to split your SFCs into `.js`, `.vue` and `.css`, you can only ena ```js // nuxt.config.js export default { - components: [ - { path: '~/components', extensions: ['vue'] } - ] + components: [{ path: '~/components', extensions: ['vue'] }] } ``` #### pattern -- Type: `string` ([glob pattern]( https://github.com/isaacs/node-glob#glob-primer)) +- Type: `string` ([glob pattern](https://github.com/isaacs/node-glob#glob-primer)) - Default: `**/*.${extensions.join(',')}` Accept Pattern that will be run against specified `path`. @@ -226,7 +222,7 @@ Accept Pattern that will be run against specified `path`. #### ignore - Type: `Array` -- Items: `string` ([glob pattern]( https://github.com/isaacs/node-glob#glob-primer)) +- Items: `string` ([glob pattern](https://github.com/isaacs/node-glob#glob-primer)) - Default: `[]` Ignore patterns that will be run against specified `path`. @@ -244,8 +240,8 @@ Example below adds `awesome-`/`Awesome` prefix to the name of components in `awe // nuxt.config.js export default { components: [ - '~/components', - { path: '~/components/awesome/', prefix: 'awesome' } + '~/components', + { path: '~/components/awesome/', prefix: 'awesome' } ] } ``` @@ -261,7 +257,7 @@ components/ ``` @@ -298,7 +294,7 @@ Level are use to define a hint when overwriting the components which have the sa export default { components: [ '~/components', // default level is 0 - { path: 'my-theme/components', level: 1 } + { path: 'my-theme/components', level: 1 } ] } ``` @@ -314,9 +310,7 @@ These properties are used in production to configure how [components with `Lazy` ```js export default { - components: [ - { path: 'my-theme/components', prefetch: true } - ] + components: [{ path: 'my-theme/components', prefetch: true }] } ``` @@ -330,6 +324,13 @@ const componets = { } ``` +#### isAsync + +- Type: Boolean +- Default: `false` unless component name ends with `.async.vue` + +This flag indicates, component should be loaded async (with a seperate chunk) regardless of using `Lazy` prefix or not. + ## Migration guide ## `v1` to `v2` @@ -337,9 +338,9 @@ const componets = { Starting with `nuxt@2.15`, Nuxt uses `@nuxt/components` v2: - All components are globally available so you can move `components/global/` -to `components/` and `global: true` is not required anymore + to `components/` and `global: true` is not required anymore - Full path inside `components` is used to prefix component names. If you were structing your -components in multiple directories, should either add prefix or register in `components` section of `nuxt.config` or use new `pathPrefix` option. + components in multiple directories, should either add prefix or register in `components` section of `nuxt.config` or use new `pathPrefix` option. **Example:** @@ -363,7 +364,7 @@ export default { '~/components/templates', '~/components/atoms', '~/components/molecules', - '~/components/organisms', + '~/components/organisms' ] } ``` @@ -393,8 +394,8 @@ Then in `awesome-ui/nuxt.js` you can use the `components:dir` hook: ```js import { join } from 'path' -export default function () { - this.nuxt.hook('components:dirs', (dirs) => { +export default function() { + this.nuxt.hook('components:dirs', dirs => { // Add ./components dir to the list dirs.push({ path: join(__dirname, 'components'), @@ -408,10 +409,7 @@ That's it! Now in your project, you can import your ui library as a Nuxt module ```js export default { - buildModules: [ - '@nuxt/components', - 'awesome-ui/nuxt' - ] + buildModules: ['@nuxt/components', 'awesome-ui/nuxt'] } ``` @@ -438,15 +436,11 @@ Next: publish your `awesome-ui` module to [npm](https://www.npmjs.com) and share [npm-version-src]: https://img.shields.io/npm/v/@nuxt/components/latest.svg?style=flat-square [npm-version-href]: https://npmjs.com/package/@nuxt/components - [npm-downloads-src]: https://img.shields.io/npm/dt/@nuxt/components.svg?style=flat-square [npm-downloads-href]: https://npmjs.com/package/@nuxt/components - [github-actions-ci-src]: https://img.shields.io/github/workflow/status/nuxt/typescript/test?label=ci&style=flat-square [github-actions-ci-href]: https://github.com/nuxt/components/actions?query=workflow%3Aci - [codecov-src]: https://img.shields.io/codecov/c/github/nuxt/components.svg?style=flat-square [codecov-href]: https://codecov.io/gh/nuxt/components - [license-src]: https://img.shields.io/npm/l/@nuxt/components.svg?style=flat-square [license-href]: https://npmjs.com/package/@nuxt/components diff --git a/package.json b/package.json index f9d6893..35af333 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ ], "scripts": { "build": "siroc build", - "dev": "nuxt-ts test/fixture", + "dev": "nuxt dev test/fixture", "lint": "eslint --ext .ts,.js,.vue .", "prepare": "yarn link && yarn link @nuxt/components", "prepublishOnly": "yarn build", diff --git a/src/index.ts b/src/index.ts index 10fc6db..36f4ba8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -74,6 +74,7 @@ const componentsModule: Module = function () { path: dirPath, extensions, pattern: dirOptions.pattern || `**/*.{${extensions.join(',')},}`, + isAsync: dirOptions.isAsync ?? !nuxt.options.dev /* async only for prod by default */, // TODO: keep test/unit/utils.ts updated ignore: [ '**/*.stories.{js,ts,jsx,tsx}', // ignore storybook files diff --git a/src/loader.ts b/src/loader.ts index e454332..124d66e 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -4,7 +4,7 @@ import { matcher } from './scan' import type { Component } from './types' function install (this: WebpackLoader.LoaderContext, content: string, components: Component[]) { - const imports = '{' + components.map(c => `${c.pascalName}: ${c.import}`).join(',') + '}' + const imports = '{' + components.map(c => `${c.pascalName}: ${c.isAsync ? c.asyncImport : c.import}`).join(',') + '}' let newContent = '/* nuxt-component-imports */\n' newContent += `installComponents(component, ${imports})\n` diff --git a/src/scan.ts b/src/scan.ts index 4a11fce..18e8c82 100644 --- a/src/scan.ts +++ b/src/scan.ts @@ -17,7 +17,7 @@ export async function scanComponents (dirs: ScanDir[], srcDir: string): Promise< const filePaths = new Set() const scannedPaths: string[] = [] - for (const { path, pattern, ignore = [], prefix, extendComponent, pathPrefix, level, prefetch = false, preload = false } of dirs.sort(sortDirsByPathLength)) { + for (const { path, pattern, ignore = [], prefix, extendComponent, pathPrefix, level, prefetch = false, preload = false, isAsync: dirIsAsync } of dirs.sort(sortDirsByPathLength)) { const resolvedNames = new Map() for (const _file of await globby(pattern!, { cwd: path, ignore })) { @@ -39,6 +39,8 @@ export async function scanComponents (dirs: ScanDir[], srcDir: string): Promise< if (fileName.toLowerCase() === 'index') { fileName = pathPrefix === false ? basename(dirname(filePath)) : '' /* inherits from path */ } + const isAsync = fileName.endsWith('.async') ? true : dirIsAsync + fileName = fileName.replace(/\.async$/, '') const fileNameParts = splitByCase(fileName) const componentNameParts: string[] = [] @@ -66,12 +68,13 @@ export async function scanComponents (dirs: ScanDir[], srcDir: string): Promise< const shortPath = relative(srcDir, filePath) const chunkName = 'components/' + kebabName - let component = { + let component: Component = { filePath, pascalName, kebabName, chunkName, shortPath, + isAsync, import: '', asyncImport: '', export: 'default', diff --git a/src/types.ts b/src/types.ts index f59c983..bf2197a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,7 +6,7 @@ export interface Component { export: string filePath: string shortPath: string - async?: boolean + isAsync?: boolean chunkName: string /** @deprecated */ global: boolean @@ -20,6 +20,7 @@ export interface ScanDir { pattern?: string | string[] ignore?: string[] prefix?: string + isAsync?: boolean /** @deprecated */ global?: boolean | 'dev' pathPrefix?: boolean diff --git a/templates/components/index.js b/templates/components/index.js index 494a6d7..6ebd6bd 100644 --- a/templates/components/index.js +++ b/templates/components/index.js @@ -1,17 +1,17 @@ import { wrapFunctional } from './utils' <%= options.getComponents().map(c => { - const exp = c.pascalName === c.export ? c.pascalName : `${c.export} as ${c.pascalName}` - return `export { ${exp} } from '../${relativeToBuild(c.filePath)}'` -}).join('\n') %> - -<%= options.getComponents().map(c => { - const exp = c.export === 'default' ? `c.default || c` : `c['${c.export}']` const magicComments = [ `webpackChunkName: "${c.chunkName}"`, c.prefetch === true || typeof c.prefetch === 'number' ? `webpackPrefetch: ${c.prefetch}` : false, c.preload === true || typeof c.preload === 'number' ? `webpackPreload: ${c.preload}` : false, ].filter(Boolean).join(', ') - - return `export const Lazy${c.pascalName} = import('../${relativeToBuild(c.filePath)}' /* ${magicComments} */).then(c => wrapFunctional(${exp}))` + if (c.isAsync) { + const exp = c.export === 'default' ? `c.default || c` : `c['${c.export}']` + const asyncImport = `import('../${relativeToBuild(c.filePath)}' /* ${magicComments} */).then(c => wrapFunctional(${exp}))` + return `export const ${c.pascalName} = ${asyncImport}` + } else { + const exp = c.export === 'default' ? `default as ${c.pascalName}` : c.pascalName + return `export { ${exp} } from '../${relativeToBuild(c.filePath)}'` + } }).join('\n') %> diff --git a/templates/components/plugin.js b/templates/components/plugin.js index 3bdefa5..3394d07 100644 --- a/templates/components/plugin.js +++ b/templates/components/plugin.js @@ -1,22 +1,7 @@ import Vue from 'vue' -import { wrapFunctional } from './utils' - -<% const components = options.getComponents() %> - -const components = { -<%= components.map(c => { - const exp = c.export === 'default' ? `c.default || c` : `c['${c.export}']` - const magicComments = [ - `webpackChunkName: "${c.chunkName}"`, - c.prefetch === true || typeof c.prefetch === 'number' ? `webpackPrefetch: ${c.prefetch}` : false, - c.preload === true || typeof c.preload === 'number' ? `webpackPreload: ${c.preload}` : false, - ].filter(Boolean).join(', ') - - return ` ${c.pascalName.replace(/^Lazy/, '')}: () => import('../${relativeToBuild(c.filePath)}' /* ${magicComments} */).then(c => wrapFunctional(${exp}))` -}).join(',\n') %> -} +import * as components from './index' for (const name in components) { Vue.component(name, components[name]) - Vue.component('Lazy' + name, components[name]) + Vue.component('Lazy' + name, () => Promise.resolve(components[name])) } diff --git a/templates/components/readme_md b/templates/components/readme_md index 58b82e8..0100fe6 100644 --- a/templates/components/readme_md +++ b/templates/components/readme_md @@ -11,6 +11,7 @@ const components = options.getComponents() const list = components.map(c => { const pascalName = c.pascalName.replace(/^Lazy/, '') const kebabName = c.kebabName.replace(/^lazy-/, '') - return `- \`<${pascalName}>\` | \`<${kebabName}>\` (${c.shortPath})` + const tags = c.isAsync ? ' [async]' : '' + return `- \`<${pascalName}>\` | \`<${kebabName}>\` (${c.shortPath})${tags}` }) %><%= list.join('\n') %> diff --git a/test/fixture/components/global/Big.vue b/test/fixture/components/global/Big.async.vue similarity index 100% rename from test/fixture/components/global/Big.vue rename to test/fixture/components/global/Big.async.vue diff --git a/test/fixture/nuxt.config.ts b/test/fixture/nuxt.config.ts index 52a8a64..1950b48 100644 --- a/test/fixture/nuxt.config.ts +++ b/test/fixture/nuxt.config.ts @@ -8,9 +8,13 @@ const config: NuxtConfig = { nuxtComponents ], + typescript: { + typeCheck: false + }, + components: [ '~/components', - { path: '~/components/global', global: true }, + { path: '~/components/global', global: true, isAsync: false }, { path: '~/components/no-prefix', pathPrefix: false }, { path: '~/components/multifile', extensions: ['vue'] }, '~/non-existent',