Skip to content

Commit

Permalink
fix: make parsePages compatible with typescript decorators
Browse files Browse the repository at this point in the history
Resolves #408
Resolves #76

Thanks to @Seb-L for the code.
  • Loading branch information
rchl committed Sep 4, 2019
1 parent a25e8c5 commit 5a3db3b
Show file tree
Hide file tree
Showing 10 changed files with 360 additions and 124 deletions.
10 changes: 10 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ jobs:
name: Type Tests
command: yarn test:types

test-unit:
executor: node
steps:
- attach-project
- run:
name: Unit Tests
command: yarn test:unit && yarn coverage

test-e2e-ssr:
executor: node
steps:
Expand Down Expand Up @@ -95,6 +103,8 @@ workflows:
requires: [setup]
- test-types:
requires: [lint]
- test-unit:
requires: [lint]
- test-e2e-ssr:
requires: [lint]
- test-e2e-browser:
Expand Down
2 changes: 1 addition & 1 deletion docs/options-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ Here are all the options available when configuring the module and their default
syncRouteParams: true
},

// By default, custom routes are extracted from page files using acorn parsing,
// By default, custom routes are extracted from page files using babel parser,
// set this to false to disable this
parsePages: true,

Expand Down
8 changes: 3 additions & 5 deletions docs/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,9 @@ To configure the strategy, use the `strategy` option. Make sure you have a `defa
In some cases, you might want to translate URLs in addition to having them prefixed with the locale code. There are 2 ways of configuring custom paths for your pages: in-component options or via the module's configuration.
> When using in-component paths options, your pages are parsed using [acorn](https://github.com/acornjs/acorn) which might fail if you're using TypeScript or advanced syntax that might not be recognized by the parser, in which case it is recommended you set your custom paths in the module's configuration instead.
### In-component options
Add a `i18n.paths` property to your page and set your custom paths there:
Add a `nuxtI18n.paths` property to your page and set your custom paths there:
```js
// pages/about.vue
Expand All @@ -99,13 +97,13 @@ export default {
### Module's configuration
Make sure you set the `parsePages` option to `false` to disable acorn parsing and add your custom paths in the `pages` option:
Make sure you set the `parsePages` option to `false` to disable babel parsing and add your custom paths in the `pages` option:
```js
// nuxt.config.js
['nuxt-i18n', {
parsePages: false, // Disable acorn parsing
parsePages: false, // Disable babel parsing
pages: {
about: {
en: '/about-us', // -> accessible at /about-us (no prefix since it's the default locale)
Expand Down
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@
"dev:basic": "nuxt -c ./test/fixture/basic/nuxt.config.js",
"coverage": "codecov",
"lint": "eslint src test",
"test": "yarn test:e2e-ssr && yarn test:e2e-browser && yarn test:types",
"test": "yarn test:types && yarn test:unit && yarn test:e2e-ssr && yarn test:e2e-browser",
"test:e2e-ssr": "jest test/module.test",
"test:e2e-browser": "jest test/browser.test",
"test:unit": "jest test/unit.test",
"test:types": "tsc -p types/test",
"release": "standard-version && git push --follow-tags && npm publish",
"docs:dev": "vuepress dev docs",
Expand Down Expand Up @@ -58,10 +59,9 @@
]
},
"dependencies": {
"@babel/parser": "^7.5.5",
"@babel/traverse": "^7.5.5",
"@kazupon/vue-i18n-loader": "^0.4.0",
"acorn": "^7.0.0",
"acorn-dynamic-import": "^4.0.0",
"acorn-walk": "^7.0.0",
"cookie": "^0.4.0",
"is-https": "^1.0.0",
"js-cookie": "^2.2.0",
Expand All @@ -73,10 +73,11 @@
"@babel/plugin-syntax-dynamic-import": "7.2.0",
"@babel/preset-env": "7.5.5",
"@babel/runtime": "7.5.5",
"@nuxt/types": "0.2.6",
"@nuxtjs/module-test-utils": "1.3.0",
"@types/jest": "24.0.18",
"babel-core": "7.0.0-bridge.0",
"babel-plugin-dynamic-import-node": "2.3.0",
"browserstack-local": "1.4.2",
"chromedriver": "76.0.1",
"codecov": "3.5.0",
"eslint": "6.3.0",
Expand Down
46 changes: 30 additions & 16 deletions src/helpers/components.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
const { readFileSync } = require('fs')
const { COMPONENT_OPTIONS_KEY } = require('./constants')
const { COMPONENT_OPTIONS_KEY, MODULE_NAME } = require('./constants')

const acorn = require('acorn')
const dynamicImport = require('acorn-dynamic-import')
const inject = require('acorn-dynamic-import/lib/walk')
const walker = inject.default(require('acorn-walk'))
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
// Must not be an explicit dependency to avoid version mismatch issue.
// See https://github.com/nuxt-community/nuxt-i18n/issues/297
const compiler = require('vue-template-compiler')
Expand All @@ -17,18 +15,34 @@ exports.extractComponentOptions = (path) => {
}

const script = Component.script.content
const parsed = acorn.Parser.extend(dynamicImport.default).parse(script, {
ecmaVersion: 10,
sourceType: 'module'
})
walker.simple(parsed, {
Property (node) {
if (node.key.name === COMPONENT_OPTIONS_KEY) {
const data = script.substring(node.start, node.end)
componentOptions = eval(`({${data}})`)[COMPONENT_OPTIONS_KEY] // eslint-disable-line

try {
const parsed = parser.parse(script, {
sourceType: 'module',
plugins: [
'classProperties',
'decorators-legacy',
'dynamicImport',
'estree',
'exportDefaultFrom',
'typescript'
]
})

traverse(parsed, {
enter (path) {
if (path.node.type === 'Property') {
if (path.node.key.name === COMPONENT_OPTIONS_KEY) {
const data = script.substring(path.node.start, path.node.end)
componentOptions = Function(`return ({${data}})`)()[COMPONENT_OPTIONS_KEY] // eslint-disable-line
}
}
}
}
}, walker.base)
})
} catch (error) {
// eslint-disable-next-line
console.warn('[' + MODULE_NAME + `] Error parsing "${COMPONENT_OPTIONS_KEY}" component option in file "${path}".`)
}

return componentOptions
}
18 changes: 18 additions & 0 deletions test/fixture/typescript/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Configuration as NuxtConfiguration } from '@nuxt/types'
import { NuxtVueI18n } from '../../../types/vue';

const nuxtI18nOptions: NuxtVueI18n.Options.AllOptionsInterface {
locales: [
{ code: 'en', iso: 'en-US', name: 'English' },
{ code: 'pl', iso: 'pl-PL', name: 'Polish' }
],
defaultLocale: 'en',
parsePages: true
}

const config: NuxtConfiguration = {
buildModules: ['@nuxt/typescript-build'],
modules: ['nuxt-i18n', nuxtI18nOptions],
};

export default config
27 changes: 27 additions & 0 deletions test/fixture/typescript/pages/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<template>
<div>
Home
</div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
@Component({
nuxtI18n: {
paths: {
pl: '/polish'
}
},
head() {
return {
title: 'Home'
}
}
})
export default class Home extends Vue {
async mounted() {
await (this as any).$message.info('This is a normal message')
}
}
</script>
20 changes: 20 additions & 0 deletions test/fixture/typescript/pages/invalidOptions.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<template>
<div>
Home
</div>
</template>

<script>
import Vue from 'vue';
function fail() {}
export default Vue.extend({
nuxtI18n: a,
head() {
return {
title: 'invalid options'
}
}
})
</script>
22 changes: 22 additions & 0 deletions test/unit.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import path from 'path'

describe('parsePages', () => {
test('parses in-component options', async () => {
const { extractComponentOptions } = await import('../src/helpers/components')
const options = extractComponentOptions(path.join(__dirname, './fixture/typescript/pages/index.vue'))
expect(options).toHaveProperty('paths')
expect(options.paths).toHaveProperty('pl')
expect(options.paths.pl).toBe('/polish')
})

test('triggers warning with invalid in-component options', async () => {
const { extractComponentOptions } = await import('../src/helpers/components')

const spy = jest.spyOn(console, 'warn').mockImplementation(() => {})
const options = extractComponentOptions(path.join(__dirname, './fixture/typescript/pages/invalidOptions.vue'))
expect(spy.mock.calls[0][0]).toContain('Error parsing')
spy.mockRestore()

expect(Object.keys(options).length).toBe(0)
})
})

0 comments on commit 5a3db3b

Please sign in to comment.