Can't set up @babel/plugin-proposal-optional-chaining


#1

Hello,

A couple months ago, when Vue CLI 3 was still in beta, I experimented on a new project with optional chaining, using @babel/plugin-proposal-optional-chaining.
I managed to set up everything and make it work. Since then I’ve abandoned the project and moved to other stuff, during which Vue CLI 3 was updated to 3.0.5.
Now, I am unable to run this same project again because of an error. It doesn’t understand the foo?.bar syntax anymore, and I can’t find why,

I’ve set up a brand new vue project (with typescript) to do some tests.
After installing vue and @babel/plugin-proposal-optional-chaining, I get the following package.json:

  "devDependencies": {
    "@babel/plugin-proposal-class-properties": "^7.1.0",
    "@babel/plugin-proposal-optional-catch-binding": "^7.0.0",
    "@babel/plugin-proposal-optional-chaining": "^7.0.0",
    "@types/chai": "^4.1.0",
    "@types/mocha": "^5.2.4",
    "@vue/cli-plugin-babel": "^3.0.5",
    "@vue/cli-plugin-pwa": "^3.0.5",
    "@vue/cli-plugin-typescript": "^3.0.5",
    "@vue/cli-plugin-unit-mocha": "^3.0.5",
    "@vue/cli-service": "^3.0.5",
    "@vue/test-utils": "^1.0.0-beta.20",
    "chai": "^4.1.2",
    "node-sass": "^4.9.0",
    "sass-loader": "^7.0.1",
    "typescript": "^3.0.0",
    "vue-template-compiler": "^2.5.17"
  }

Note that I have also installed @babel/plugin-proposal-optional-catch-binding, in order to test if other plugins work (and it does work).

My babel.config.js file is as follows:

module.exports = {
  presets: [
    '@vue/app'
  ],
  plugins: [
    ["@babel/plugin-proposal-optional-catch-binding"],
    ["@babel/plugin-proposal-optional-chaining"]
  ]
}

My main.ts file contains the following code:

const obj = {
  foo: {
    bar: {
      baz: 111,
    },
  },
};

console.log(
  obj?.foo?.bar
)

When starting the server with npm run serve, I get the following error in the console:

 WAIT  Compiling...                                                                             12:49:26 PM
 94% after seal

 ERROR  Failed to compile with 1 errors                                                         12:49:27 PM
 error  in ./src/main.ts

Syntax Error: SyntaxError: C:\Node.js\next2\client2\src\main.ts: Unexpected token (9:18)

   7 |     },
   8 | };
>  9 | console.log(obj ? .foo ? .bar
     |                   ^
  10 |     :
  11 |     :
  12 | );


 @ multi (webpack)-dev-server/client?http://192.168.1.112:8080/sockjs-node (webpack)/hot/dev-server.js ./src/main.ts
                                                                                            Type checking in progress...
No type errors found
Version: typescript 3.1.3
Time: 929ms

I don’t understand why it doesn’t work.
I have tested this plug-in on my other node server (with Babel 7.1) and it works great. It’s only when using Vue CLI that I can’t have it work.
The other plugin works fine, so I don’t think that it’s a problem in babel.config.js.

Maybe I just don’t see the bug, or maybe the plugin is not compatible with the current version of Vue?
An idea?
Thanks!


#2

Well, the Babel plugin surely works, but typescript doesn’t support this syntax.


#3

oh, I didn’t see that.
Thanks for the info. I’ll forget about this plugin for now!


#4

Hi,
I’ve got similar problem to y3018, but I have files which are ts only or js only. My question is: is it possible to disable typescript compilation for .vue files containing <script lang="js"> ?

For example Status.vue:

<script lang="js">
....
healthchecks (state) {
  return state.backendsHealthchecks.find((a => a.backendSlug === this.backendSlug))?.healthchecks
}
</script>

notice ?. inside function.


#5

Those files are not compiled by typescript


#6

Right. I had an error because of tsconfig.json:

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "baseUrl": ".",
    "types": [
      "webpack-env",
      "mocha",
      "chai"
    ],
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    //"src/**/*.vue",
    //"src/**/*.ts.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ],
  "exclude": [
    "./node_modules"
  ]
}

Removing “src/**/*.vue” line solved my problem.


#7

So just found this searching for if it was possible because I wanted to try typescript but I did not want to give up some < stage 3 proposals, and now that I managed to figure it out I thought I’d comment for the OP and anyone else who’s searching for how to do this (and future me probably).

The solution feels like a bit of a mess, but I cannot give up optional-chains! Once you try them you can’t go back.

So before setting up my project I had tried this using just babel (no vue, etc) and it had worked, so I knew it was possible, but there doesn’t seem to be a plugin or an option for this. It seems the vue typescript plugin and the way its setup (even when using babel) is that it first transpiles the typescript then passes it to babel (if I understood correctly).

That’s why < stage 3 proposals won’t work. To use them you have to use babel to transpile the typescript as well. This is not a perfect solution, look at the babel docs for the typescript-preset for some of the smaller limitations, also if you care about class-style components, there’s more annoying limitation, vue-property-decorators specific decorators don’t seem to work. You’ll have to change the @Prop() part in the example code to get it working. Better to change to just use vue-class-components but you will have to change all the imports in the example.

Note that when you use things like the optional-chain proposal, if you’re using VS Code it will complain it’s wrong but things will still compile.

Anyways to do this, I created the project with and without typescript. From the typescript one I copied the src and test folders, the tsconfig.js and any test config files.

Then in the one without typescript I installed:

@babel/preset-typescript
// other plugins you want (e.g. optional-chaining)

If you want to use the class style components you’ll also need:

@babel/plugin-proposal-decorators
//and EITHER:
vue-property-decorator // even if it doesn't work, it requires vue-class-component under the hood and will work without having to change that much demo code
// or you can install:
vue-class-component //but you'll need to edit all the imports in the demo

The babel config should look something like:

module.exports = {
	presets: [
		"@vue/app",
		"@babel/preset-typescript"
	],
	plugins: [
		"@babel/plugin-proposal-optional-chaining",
		//if you want class style components: 
		["@babel/plugin-proposal-decorators", { "legacy": true }], 
	]
}

And in .eslintrc if you’re using it, the parserOptions should have:

parserOptions: {
	parser: 'babel-eslint',
	ecmaFeatures: {
		legacyDecorators: true
	}
},

If eslint is not working in .ts files in VS Code add:

	"eslint.validate": {
		// ... existing
		{
			"language": "typescript",
			"autoFix": true
		},
		{
			"language": "typescriptvue",
			"autoFix": true
		}
	}

And finally you’ll need to make a vue.config.js file with:

module.exports = {
	chainWebpack: config => {
		// manually adds typescript support
		// tells webpack it should process the .ts extension
		config.resolve.extensions
			.add(".ts")
		// changes entry point from js to ts file
		config
			.entry("app")
			.clear()
		config
			.entry("app")
			.add("./src/main.ts")
		//adds rule to pass .ts files to babel-loader
		config.module
			.rule("ts")
			.test(/\.ts$/)
			.use("babel-loader")
			.loader("babel-loader")
			.end()
	}
}

Oh and you’ll also need to install the types for the tests (e.g. @types/jest) and, for example, for jest, in the config you need to change the last transform to "^.+\\.tsx?$": "babel-jest" and remove the globals option.

And even then it might not work. If you have jest auto-running in your IDE, you might need to stop it (really you should stop it before starting, the jest runner kept crashing VS Code for me), then jest --clearCache somehow (e.g. npx jest --clearCache, not sure if a global jest install will work) and also add this to the jest config at the top:

process.env.VUE_CLI_BABEL_TARGET_NODE = true
process.env.VUE_CLI_BABEL_TRANSPILE_MODULES = true

and run the tests with npm run test:unit or you might have to fiddle with your IDE (I had to tell VS Code’s jest runner that jest was in node_modules/.bin/jest for some reason) to get jest auto-running again.

And if I didn’t forget anything, and you haven’t regretted attempting this halfway through, it should work.