diff --git a/.eslintrc.js b/.eslintrc.js
index ef6c85f147..ed08d4468a 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -2,7 +2,10 @@ module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint',
- sourceType: 'module'
+ sourceType: 'module',
+ ecmaFeatures: {
+ legacyDecorators: true
+ }
},
extends: [
'@nuxtjs'
diff --git a/package.json b/package.json
index aff7d145fd..4e2026923b 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/packages/babel-preset-app/package.json b/packages/babel-preset-app/package.json
index dc83114255..a10148332e 100644
--- a/packages/babel-preset-app/package.json
+++ b/packages/babel-preset-app/package.json
@@ -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"
diff --git a/packages/babel-preset-app/src/index.js b/packages/babel-preset-app/src/index.js
index 79add0f622..84888046b7 100644
--- a/packages/babel-preset-app/src/index.js
+++ b/packages/babel-preset-app/src/index.js
@@ -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'), {
diff --git a/packages/builder/src/builder.js b/packages/builder/src/builder.js
index 81742d127c..f654dede2e 100644
--- a/packages/builder/src/builder.js
+++ b/packages/builder/src/builder.js
@@ -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)
)
}
diff --git a/packages/common/src/utils.js b/packages/common/src/utils.js
index 5f890e84d5..327a1b0285 100644
--- a/packages/common/src/utils.js
+++ b/packages/common/src/utils.js
@@ -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) {
diff --git a/packages/config/src/config/build.js b/packages/config/src/config/build.js
index 12381d7079..bf1b57891a 100644
--- a/packages/config/src/config/build.js
+++ b/packages/config/src/config/build.js
@@ -5,6 +5,7 @@ export default () => ({
analyze: false,
profile: process.argv.includes('--profile'),
extractCSS: false,
+ typescript: false,
crossorigin: undefined,
cssSourceMap: undefined,
ssr: undefined,
diff --git a/packages/config/src/options.js b/packages/config/src/options.js
index 178fdc3227..05b8ca1274 100644
--- a/packages/config/src/options.js
+++ b/packages/config/src/options.js
@@ -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))
diff --git a/packages/webpack/src/config/base.js b/packages/webpack/src/config/base.js
index 3ef9db51cb..3a4d6f3b46 100644
--- a/packages/webpack/src/config/base.js
+++ b/packages/webpack/src/config/base.js
@@ -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
},
diff --git a/test/fixtures/typescript/@types/process.d.ts b/test/fixtures/typescript/@types/process.d.ts
new file mode 100644
index 0000000000..648fe143b2
--- /dev/null
+++ b/test/fixtures/typescript/@types/process.d.ts
@@ -0,0 +1,8 @@
+declare namespace NodeJS {
+ interface Process {
+ browser: boolean
+ client: boolean
+ server: boolean
+ static: boolean
+ }
+}
diff --git a/test/fixtures/typescript/@types/shims-tsx.d.ts b/test/fixtures/typescript/@types/shims-tsx.d.ts
new file mode 100644
index 0000000000..22d8ef98a9
--- /dev/null
+++ b/test/fixtures/typescript/@types/shims-tsx.d.ts
@@ -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
+ }
+ }
+}
diff --git a/test/fixtures/typescript/layouts/default.vue b/test/fixtures/typescript/layouts/default.vue
new file mode 100644
index 0000000000..0526cdec9f
--- /dev/null
+++ b/test/fixtures/typescript/layouts/default.vue
@@ -0,0 +1,13 @@
+
+