Skip to content

Commit

Permalink
feat: explicit async loading (#212)
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 committed Aug 11, 2021
1 parent 64e10cc commit 5adefb9
Show file tree
Hide file tree
Showing 11 changed files with 61 additions and 72 deletions.
74 changes: 34 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -88,18 +89,18 @@ You now can easily import a component on-demand:
</template>

<script>
export default {
data () {
return {
foo: null
}
},
methods: {
async loadFoo () {
this.foo = await this.$axios.$get('foo')
export default {
data() {
return {
foo: null
}
},
methods: {
async loadFoo() {
this.foo = await this.$axios.$get('foo')
}
}
}
}
</script>
```

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -169,7 +167,7 @@ export default {
components: [
'~/components', // shortcut to { path: '~/components' }
{ path: '~/components/awesome/', prefix: 'awesome' }
],
]
}
```

Expand Down Expand Up @@ -210,23 +208,21 @@ 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`.

#### 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`.
Expand All @@ -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' }
]
}
```
Expand All @@ -261,7 +257,7 @@ components/
<template>
<div>
<AwesomeButton>Click on me 🤘</AwesomeButton>
<Button>Click on me</Button>
<button>Click on me</button>
</div>
</template>
```
Expand Down Expand Up @@ -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 }
]
}
```
Expand All @@ -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 }]
}
```

Expand All @@ -330,16 +324,23 @@ 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`

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:**

Expand All @@ -363,7 +364,7 @@ export default {
'~/components/templates',
'~/components/atoms',
'~/components/molecules',
'~/components/organisms',
'~/components/organisms'
]
}
```
Expand Down Expand Up @@ -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'),
Expand All @@ -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']
}
```

Expand All @@ -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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const componentsModule: Module<Options> = 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
Expand Down
2 changes: 1 addition & 1 deletion src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
7 changes: 5 additions & 2 deletions src/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export async function scanComponents (dirs: ScanDir[], srcDir: string): Promise<
const filePaths = new Set<string>()
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<string, string>()

for (const _file of await globby(pattern!, { cwd: path, ignore })) {
Expand All @@ -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[] = []
Expand Down Expand Up @@ -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',
Expand Down
3 changes: 2 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface Component {
export: string
filePath: string
shortPath: string
async?: boolean
isAsync?: boolean
chunkName: string
/** @deprecated */
global: boolean
Expand All @@ -20,6 +20,7 @@ export interface ScanDir {
pattern?: string | string[]
ignore?: string[]
prefix?: string
isAsync?: boolean
/** @deprecated */
global?: boolean | 'dev'
pathPrefix?: boolean
Expand Down
16 changes: 8 additions & 8 deletions templates/components/index.js
Original file line number Diff line number Diff line change
@@ -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') %>
19 changes: 2 additions & 17 deletions templates/components/plugin.js
Original file line number Diff line number Diff line change
@@ -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]))
}
3 changes: 2 additions & 1 deletion templates/components/readme_md
Original file line number Diff line number Diff line change
Expand Up @@ -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') %>
File renamed without changes.
6 changes: 5 additions & 1 deletion test/fixture/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down

0 comments on commit 5adefb9

Please sign in to comment.