feat(builder): optional typescript support (#4557)

This commit is contained in:
Kevin Marrec 2018-12-15 07:55:08 +01:00 committed by Pooya Parsa
parent 99614535b5
commit 7145c1ab5d
22 changed files with 211 additions and 18 deletions

View File

@ -2,7 +2,10 @@ module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint',
sourceType: 'module'
sourceType: 'module',
ecmaFeatures: {
legacyDecorators: true
}
},
extends: [
'@nuxtjs'

View File

@ -75,7 +75,8 @@
"rollup-plugin-replace": "^2.1.0",
"sort-package-json": "^1.17.0",
"typescript": "^3.2.2",
"vue-jest": "^3.0.2"
"vue-jest": "^3.0.2",
"vue-property-decorator": "^7.2.0"
},
"repository": {
"type": "git",

View File

@ -17,6 +17,7 @@
"@babel/plugin-syntax-jsx": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/preset-env": "^7.2.0",
"@babel/preset-typescript": "^7.1.0",
"@babel/runtime": "^7.2.0",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-plugin-transform-vue-jsx": "^4.0.1"

View File

@ -92,6 +92,11 @@ module.exports = (context, options = {}) => {
}
])
// TypeScript preset
if (options.typescript) {
presets.push(require('@babel/preset-typescript'))
}
plugins.push(
require('@babel/plugin-syntax-dynamic-import'),
[require('@babel/plugin-proposal-decorators'), {

View File

@ -44,6 +44,11 @@ export default class Builder {
restart: null
}
this.supportedExtensions = ['vue', 'js']
if (this.options.build.typescript) {
this.supportedExtensions.push('ts', 'tsx')
}
// Helper to resolve build paths
this.relativeToBuild = (...args) =>
relativeTo(this.options.buildDir, ...args)
@ -265,14 +270,14 @@ export default class Builder {
// -- Layouts --
if (fsExtra.existsSync(path.resolve(this.options.srcDir, this.options.dir.layouts))) {
const layoutsFiles = await glob(`${this.options.dir.layouts}/**/*.{vue,js}`, {
const layoutsFiles = await glob(`${this.options.dir.layouts}/**/*.{${this.supportedExtensions.join(',')}}`, {
cwd: this.options.srcDir,
ignore: this.options.ignore
})
layoutsFiles.forEach((file) => {
const name = file
.replace(new RegExp(`^${this.options.dir.layouts}/`), '')
.replace(/\.(vue|js)$/, '')
.replace(new RegExp(`\\.(${this.supportedExtensions.join('|')})$`), '')
if (name === 'error') {
if (!templateVars.components.ErrorPage) {
templateVars.components.ErrorPage = this.relativeToBuild(
@ -308,11 +313,11 @@ export default class Builder {
} else if (this._nuxtPages) {
// Use nuxt.js createRoutes bases on pages/
const files = {}
; (await glob(`${this.options.dir.pages}/**/*.{vue,js}`, {
; (await glob(`${this.options.dir.pages}/**/*.{${this.supportedExtensions.join(',')}}`, {
cwd: this.options.srcDir,
ignore: this.options.ignore
})).forEach((f) => {
const key = f.replace(/\.(js|vue)$/, '')
const key = f.replace(new RegExp(`\\.(${this.supportedExtensions.join('|')})$`), '')
if (/\.vue$/.test(f) || !files[key]) {
files[key] = f.replace(/(['"])/g, '\\$1')
}
@ -320,7 +325,8 @@ export default class Builder {
templateVars.router.routes = createRoutes(
Object.values(files),
this.options.srcDir,
this.options.dir.pages
this.options.dir.pages,
this.supportedExtensions
)
} else { // If user defined a custom method to create routes
templateVars.router.routes = this.options.build.createRoutes(
@ -502,20 +508,19 @@ export default class Builder {
watchClient() {
const src = this.options.srcDir
const rGlob = dir => ['*', '**/*'].map(glob => r(src, `${dir}/${glob}.{${this.supportedExtensions.join(',')}}`))
let patterns = [
r(src, this.options.dir.layouts),
r(src, this.options.dir.store),
r(src, this.options.dir.middleware),
r(src, `${this.options.dir.layouts}/*.{vue,js}`),
r(src, `${this.options.dir.layouts}/**/*.{vue,js}`)
...rGlob(this.options.dir.layouts)
]
if (this._nuxtPages) {
patterns.push(
r(src, this.options.dir.pages),
r(src, `${this.options.dir.pages}/*.{vue,js}`),
r(src, `${this.options.dir.pages}/**/*.{vue,js}`)
...rGlob(this.options.dir.pages)
)
}

View File

@ -315,12 +315,12 @@ const sortRoutes = function sortRoutes(routes) {
return routes
}
export const createRoutes = function createRoutes(files, srcDir, pagesDir) {
export const createRoutes = function createRoutes(files, srcDir, pagesDir, supportedExtensions = ['vue', 'js']) {
const routes = []
files.forEach((file) => {
const keys = file
.replace(RegExp(`^${pagesDir}`), '')
.replace(/\.(vue|js)$/, '')
.replace(new RegExp(`^${pagesDir}`), '')
.replace(new RegExp(`\\.(${supportedExtensions.join('|')})$`), '')
.replace(/\/{2,}/g, '/')
.split('/')
.slice(1)
@ -334,7 +334,7 @@ export const createRoutes = function createRoutes(files, srcDir, pagesDir) {
? route.name + '-' + sanitizedKey
: sanitizedKey
route.name += key === '_' ? 'all' : ''
route.chunkName = file.replace(/\.(vue|js)$/, '')
route.chunkName = file.replace(new RegExp(`\\.(${supportedExtensions.join('|')})$`), '')
const child = parent.find(parentRoute => parentRoute.name === route.name)
if (child) {

View File

@ -5,6 +5,7 @@ export default () => ({
analyze: false,
profile: process.argv.includes('--profile'),
extractCSS: false,
typescript: false,
crossorigin: undefined,
cssSourceMap: undefined,
ssr: undefined,

View File

@ -119,6 +119,9 @@ export function getNuxtConfig(_options) {
)
const mandatoryExtensions = ['js', 'mjs']
if (options.build.typescript) {
mandatoryExtensions.push('ts')
}
options.extensions = mandatoryExtensions
.filter(ext => !options.extensions.includes(ext))

View File

@ -75,7 +75,8 @@ export default class WebpackBaseConfig {
[
require.resolve('@nuxt/babel-preset-app'),
{
buildTarget: this.isServer ? 'server' : 'client'
buildTarget: this.isServer ? 'server' : 'client',
typescript: this.options.build.typescript
}
]
]
@ -215,7 +216,7 @@ export default class WebpackBaseConfig {
]
},
{
test: /\.jsx?$/,
test: this.options.build.typescript ? /\.(j|t)sx?$/ : /\.jsx?$/,
exclude: (file) => {
// not exclude files outside node_modules
if (!/node_modules/.test(file)) {
@ -376,6 +377,10 @@ export default class WebpackBaseConfig {
config() {
// Prioritize nested node_modules in webpack search path (#2558)
const webpackModulesDir = ['node_modules'].concat(this.options.modulesDir)
let extensionsToResolve = ['.wasm', '.mjs', '.js', '.json', '.vue', '.jsx']
if (this.options.build.typescript) {
extensionsToResolve = extensionsToResolve.concat(['.ts', '.tsx'])
}
const config = {
name: this.name,
@ -388,7 +393,7 @@ export default class WebpackBaseConfig {
hints: this.options.dev ? false : 'warning'
},
resolve: {
extensions: ['.wasm', '.mjs', '.js', '.json', '.vue', '.jsx'],
extensions: extensionsToResolve,
alias: this.alias(),
modules: webpackModulesDir
},

View File

@ -0,0 +1,8 @@
declare namespace NodeJS {
interface Process {
browser: boolean
client: boolean
server: boolean
static: boolean
}
}

View File

@ -0,0 +1,11 @@
import Vue, { VNode } from 'vue'
declare global {
namespace JSX {
interface Element extends VNode {}
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elemName: string]: any
}
}
}

View File

@ -0,0 +1,13 @@
<template>
<Nuxt />
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
@Component({
name: 'DefaultLayout',
middleware: 'test'
})
export default class extends Vue {}
</script>

View File

@ -0,0 +1 @@
export default () => {}

View File

@ -0,0 +1,8 @@
export default {
plugins: [
'~/plugins/plugin.ts'
],
build: {
typescript: true
}
}

View File

@ -0,0 +1,8 @@
import Vue from 'vue'
export default Vue.extend({
name: 'About',
render (h) {
return h('div', 'About Page')
}
})

View File

@ -0,0 +1,13 @@
import Vue from 'vue'
export default Vue.extend({
name: 'Contact',
data () {
return {
message: 'Contact Page'
}
},
render () {
return <div>{this.message}</div>
}
})

View File

@ -0,0 +1,14 @@
<template>
<div>{{ message }}</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
@Component({
name: 'Index'
})
export default class extends Vue {
message = 'Index Page'
}
</script>

View File

@ -0,0 +1 @@
export default () => {}

22
test/fixtures/typescript/tsconfig.json vendored Normal file
View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"lib": ["esnext", "esnext.asynciterable", "dom"],
"esModuleInterop": true,
"experimentalDecorators": true,
"allowJs": true,
"jsx": "preserve",
"sourceMap": true,
"strict": true,
"noImplicitAny": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"baseUrl": ".",
"paths": {
"~/*": ["./*"]
}
}
}

View File

@ -0,0 +1,3 @@
import { buildFixture } from '../../utils/build'
buildFixture('typescript')

View File

@ -0,0 +1,32 @@
import { loadFixture, getPort, Nuxt } from '../utils'
let nuxt = null
describe('typescript', () => {
beforeAll(async () => {
const options = await loadFixture('typescript')
nuxt = new Nuxt(options)
const port = await getPort()
await nuxt.server.listen(port, '0.0.0.0')
})
test('/', async () => {
const { html } = await nuxt.server.renderRoute('/')
expect(html).toContain('<div>Index Page</div>')
})
test('/about', async () => {
const { html } = await nuxt.server.renderRoute('/about')
expect(html).toContain('<div>About Page</div>')
})
test('/contact', async () => {
const { html } = await nuxt.server.renderRoute('/contact')
expect(html).toContain('<div>Contact Page</div>')
})
// Close server and ask nuxt to stop listening to file changes
afterAll(async () => {
await nuxt.close()
})
})

View File

@ -354,6 +354,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-typescript@^7.2.0":
version "7.2.0"
resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.2.0.tgz#55d240536bd314dcbbec70fd949c5cabaed1de29"
integrity sha512-WhKr6yu6yGpGcNMVgIBuI9MkredpVc7Y3YR4UzEZmDztHoL6wV56YBHLhWnjO1EvId1B32HrD3DRFc+zSoKI1g==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-transform-arrow-functions@^7.2.0":
version "7.2.0"
resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz#9aeafbe4d6ffc6563bf8f8372091628f00779550"
@ -570,6 +577,14 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-transform-typescript@^7.1.0":
version "7.2.0"
resolved "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.2.0.tgz#bce7c06300434de6a860ae8acf6a442ef74a99d1"
integrity sha512-EnI7i2/gJ7ZNr2MuyvN2Hu+BHJENlxWte5XygPvfj/MbvtOkWor9zcnHpMMQL2YYaaCcqtIvJUyJ7QVfoGs7ew==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-typescript" "^7.2.0"
"@babel/plugin-transform-unicode-regex@^7.2.0":
version "7.2.0"
resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.2.0.tgz#4eb8db16f972f8abb5062c161b8b115546ade08b"
@ -634,6 +649,14 @@
js-levenshtein "^1.1.3"
semver "^5.3.0"
"@babel/preset-typescript@^7.1.0":
version "7.1.0"
resolved "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.1.0.tgz#49ad6e2084ff0bfb5f1f7fb3b5e76c434d442c7f"
integrity sha512-LYveByuF9AOM8WrsNne5+N79k1YxjNB6gmpCQsnuSBAcV8QUeB+ZUxQzL7Rz7HksPbahymKkq2qBR+o36ggFZA==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-transform-typescript" "^7.1.0"
"@babel/register@^7.0.0":
version "7.0.0"
resolved "https://registry.npmjs.org/@babel/register/-/register-7.0.0.tgz#fa634bae1bfa429f60615b754fc1f1d745edd827"
@ -11037,6 +11060,11 @@ void-elements@^2.0.1:
resolved "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
vue-class-component@^6.2.0:
version "6.3.2"
resolved "https://registry.npmjs.org/vue-class-component/-/vue-class-component-6.3.2.tgz#e6037e84d1df2af3bde4f455e50ca1b9eec02be6"
integrity sha512-cH208IoM+jgZyEf/g7mnFyofwPDJTM/QvBNhYMjqGB8fCsRyTf68rH2ISw/G20tJv+5mIThQ3upKwoL4jLTr1A==
vue-eslint-parser@^4.0.2:
version "4.0.2"
resolved "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-4.0.2.tgz#7d10ec5b67d9b2ef240cac0f0e8b2f03773d810e"
@ -11095,6 +11123,13 @@ vue-no-ssr@^1.1.0:
resolved "https://registry.npmjs.org/vue-no-ssr/-/vue-no-ssr-1.1.0.tgz#b323807112f676324d9d7cfde85d7831ced11dd9"
integrity sha512-prJ9czuPrVu0GhUZKTS/epFfM15QjLuG6wt61g0nyixPXk0g6eY7wNF4RKIqJsxomOiuSYTv3Zo8A43Vi93xfw==
vue-property-decorator@^7.2.0:
version "7.2.0"
resolved "https://registry.npmjs.org/vue-property-decorator/-/vue-property-decorator-7.2.0.tgz#8e6b0f4dcc630c357135f76366ba7bd91f1db015"
integrity sha512-sCI6NVM3tEDg+mpZrQlgkddtxd9LbFWetue8D+nqO3agfSLz0KoC/UIi2P1l5E0TDhcUeIXS9rasuP2HWg+L4w==
dependencies:
vue-class-component "^6.2.0"
vue-router@^3.0.2:
version "3.0.2"
resolved "https://registry.npmjs.org/vue-router/-/vue-router-3.0.2.tgz#dedc67afe6c4e2bc25682c8b1c2a8c0d7c7e56be"