mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-23 14:15:13 +00:00
feat(builder): optional typescript support (#4557)
This commit is contained in:
parent
99614535b5
commit
7145c1ab5d
@ -2,7 +2,10 @@ module.exports = {
|
||||
root: true,
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint',
|
||||
sourceType: 'module'
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
legacyDecorators: true
|
||||
}
|
||||
},
|
||||
extends: [
|
||||
'@nuxtjs'
|
||||
|
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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'), {
|
||||
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -5,6 +5,7 @@ export default () => ({
|
||||
analyze: false,
|
||||
profile: process.argv.includes('--profile'),
|
||||
extractCSS: false,
|
||||
typescript: false,
|
||||
crossorigin: undefined,
|
||||
cssSourceMap: undefined,
|
||||
ssr: undefined,
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
},
|
||||
|
8
test/fixtures/typescript/@types/process.d.ts
vendored
Normal file
8
test/fixtures/typescript/@types/process.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
declare namespace NodeJS {
|
||||
interface Process {
|
||||
browser: boolean
|
||||
client: boolean
|
||||
server: boolean
|
||||
static: boolean
|
||||
}
|
||||
}
|
11
test/fixtures/typescript/@types/shims-tsx.d.ts
vendored
Normal file
11
test/fixtures/typescript/@types/shims-tsx.d.ts
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
13
test/fixtures/typescript/layouts/default.vue
vendored
Normal file
13
test/fixtures/typescript/layouts/default.vue
vendored
Normal 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>
|
1
test/fixtures/typescript/middleware/middleware.ts
vendored
Normal file
1
test/fixtures/typescript/middleware/middleware.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export default () => {}
|
8
test/fixtures/typescript/nuxt.config.js
vendored
Normal file
8
test/fixtures/typescript/nuxt.config.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
export default {
|
||||
plugins: [
|
||||
'~/plugins/plugin.ts'
|
||||
],
|
||||
build: {
|
||||
typescript: true
|
||||
}
|
||||
}
|
8
test/fixtures/typescript/pages/about.ts
vendored
Normal file
8
test/fixtures/typescript/pages/about.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'About',
|
||||
render (h) {
|
||||
return h('div', 'About Page')
|
||||
}
|
||||
})
|
13
test/fixtures/typescript/pages/contact.tsx
vendored
Normal file
13
test/fixtures/typescript/pages/contact.tsx
vendored
Normal 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>
|
||||
}
|
||||
})
|
14
test/fixtures/typescript/pages/index.vue
vendored
Normal file
14
test/fixtures/typescript/pages/index.vue
vendored
Normal 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>
|
1
test/fixtures/typescript/plugins/plugin.ts
vendored
Normal file
1
test/fixtures/typescript/plugins/plugin.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export default () => {}
|
22
test/fixtures/typescript/tsconfig.json
vendored
Normal file
22
test/fixtures/typescript/tsconfig.json
vendored
Normal 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": {
|
||||
"~/*": ["./*"]
|
||||
}
|
||||
}
|
||||
}
|
3
test/fixtures/typescript/typescript.test.js
vendored
Normal file
3
test/fixtures/typescript/typescript.test.js
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
import { buildFixture } from '../../utils/build'
|
||||
|
||||
buildFixture('typescript')
|
32
test/unit/typescript.test.js
Normal file
32
test/unit/typescript.test.js
Normal 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()
|
||||
})
|
||||
})
|
35
yarn.lock
35
yarn.lock
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user