Vue3 Typescript 破坏了 webpack encore watcher

Vue3 Typescript breaks the webpack encore watcher

我有一个 vue3/symfony 项目,我开始实施打字稿,但我遇到了一个我无法解决的问题。对于构建资产,我正在使用 webpack encore,当我启动观察程序时,资产编译得很好,但是当我更改 .vue 文件中的任何内容时(甚至添加空白 space 以强制 webpack 重新编译)我得到此错误:

TS2614: Module '"resources/ts/helpers"' has no exported member 'TestClass'. Did you mean to use 'import TestClass from "resources/ts/helpers"' instead?

TS2339: Property '__file' does not exist on type '{}'.

重要说明:如果我在 helper.ts 上进行任何类型的更改(即使是空白 space),编译也会再次成功。

这只会发生在导入 .vue 文件的 .ts 文件中。我导入的 .js 或 .vue(有或没有打字稿)文件都很好

helpers.ts:

export class TestClass {
  constructor(public test: string) {
  }
}

导入为 import { TestClass } from "resources/ts/helpers";

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "noImplicitThis": true,
    "jsx": "preserve",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ],
    "baseUrl": ".",
    "paths": {
      "resources/*": [
        "resources/*"
      ]
    }
  },
  "include": [
    "resources/**/*.ts",
    "resources/**/*.tsx",
    "resources/**/*.vue",
  ],
  "exclude": [
    "node_modules"
  ]
}

webpack.config.js:

const Encore = require('@symfony/webpack-encore');
const path = require('path');
const webpack = require('webpack');

// Manually configure the runtime environment if not already configured yet by the "encore" command.
// It's useful when you use tools that rely on webpack.config.js file.
if (!Encore.isRuntimeEnvironmentConfigured()) {
  Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
}

Encore
  // directory where compiled assets will be stored
  .setOutputPath('public/build/')
  // public path used by the web server to access the output path
  .setPublicPath('/build')
  // only needed for CDN's or sub-directory deploy
  //.setManifestKeyPrefix('build/')
  .copyFiles({
    from: './resources/assets/media',
    to: 'media/[path][name].[ext]',
    pattern: /\.(png|jpg|jpeg|svg)$/
  })
  .copyFiles({
    from: './resources/assets/fonts',
    to: 'fonts/[path][name].[ext]',
    pattern: /\.(ttf)$/
  })
  /*
   * ENTRY CONFIG
   *
   * Add 1 entry for each "page" of your app
   * (including one that's included on every page - e.g. "app")
   *
   * Each entry will result in one JavaScript file (e.g. main.js)
   * and one CSS file (e.g. app.css) if you JavaScript imports CSS.
   */
  .addEntry('main', './resources/main.js')

  // When enabled, Webpack "splits" your files into smaller pieces for greater optimization.
  .splitEntryChunks()

  // will require an extra script tag for runtime.js
  // but, you probably want this, unless you're building a single-page app
  .enableSingleRuntimeChunk()

  /*
   * FEATURE CONFIG
   *
   * Enable & configure other features below. For a full
   * list of features, see:
   * https://symfony.com/doc/current/frontend.html#adding-more-features
   */
  .cleanupOutputBeforeBuild()
  .enableBuildNotifications()
  .enableSourceMaps(!Encore.isProduction())
  // enables hashed filenames (e.g. app.abc123.css)
  .enableVersioning(Encore.isProduction())

  // enables @babel/preset-env polyfills
  .configureBabel(() => {
  }, {
    useBuiltIns: 'usage',
    corejs: 3
  })

  // enables Sass/SCSS support
  .enableSassLoader()

  // enables Vue support
  .enableVueLoader(() => {
  }, {
    version: 3,
    runtimeCompilerBuild: false //if using only single file components, this is not needed (https://symfony.com/doc/current/frontend/encore/vuejs.html#runtime-compiler-build)

  })
  // uncomment if you use TypeScript
  .enableTypeScriptLoader()

  // uncomment if you're having problems with a jQuery plugin
  .autoProvidejQuery()
  .addAliases({
    'resources': path.resolve('./resources')
  })
;

module.exports = Encore.getWebpackConfig();

package.json:

{
  "devDependencies": {
    "@symfony/webpack-encore": "^1.5.0",
    "@types/jquery": "^3.5.5",
    "@vue/compiler-sfc": "^3.0.2",
    "babel-core": "^7.0.0-bridge.0",
    "file-loader": "^6.0.0",
    "https-proxy-agent": "^2.2.1",
    "lorem-ipsum": "^2.0.3",
    "sass": "^1.32.13",
    "sass-loader": "^10.2.0",
    "ts-loader": "^8.3.0",
    "tslib": "^2.3.0",
    "vue-loader": "^16.5.0",
    "vue-template-compiler": "^2.6.12",
    "webpack-notifier": "^1.6.0"
  },
  "license": "UNLICENSED",
  "private": true,
  "scripts": {
    "dev-server": "encore dev-server",
    "dev": "encore dev",
    "watch": "encore dev --watch",
    "build": "encore production --progress"
  },
  "dependencies": {
    "@babel/polyfill": "^7.12.1",
    "@ckeditor/ckeditor5-build-classic": "^25.0.0",
    "@fortawesome/fontawesome-free": "^5.15.3",
    "@popperjs/core": "^2.5.4",
    "@tinymce/tinymce-vue": "^4.0.0",
    "@vee-validate/rules": "^4.2.4",
    "@vueform/multiselect": "^2.0.1",
    "axios": "^0.21.1",
    "bootstrap": "^5.0.2",
    "chart.js": "^2.9.4",
    "core-js": "^3.6.5",
    "dropzone": "^5.9.2",
    "element-plus": "^1.0.2-beta.36",
    "es6-promise": "^4.2.8",
    "inputmask": "^5.0.6",
    "jquery": "^3.5.1",
    "lodash": "^4.17.20",
    "nprogress": "^0.2.0",
    "perfect-scrollbar": "^1.5.0",
    "select2": "^4.0.13",
    "sweetalert2": "^10.10.0",
    "typescript": "^4.3.4",
    "vee-validate": "^4.5.0-alpha.2",
    "vue": "^3.0.7",
    "vue-inline-svg": "^3.0.0-beta.2",
    "vue-router": "^4.0.3",
    "vuex": "^4.0.0-rc.1",
    "yup": "^0.29.3"
  }
}

我能看到的一个可能的问题是这一行:

.addEntry('main', './resources/main.js')

在使用 typescript 的 Encore docs 中,他们将条目设置为 .ts 文件:

.addEntry('main', './assets/main.ts')

如果您有一个 main.ts 文件,那么这可以解释为什么编辑 helper.ts 会导致它再次工作。编辑文件会触发打字稿编译器重新编译(从 .ts 转译 -> .js),并创建一个 main.js 文件(之前可能不存在)。

您似乎正在将 Vue Javascript 项目迁移到 Vue 打字稿项目中。因此,我们必须明智地配置 webpack 和 tsconfig。我们必须按照一些步骤正确配置它。

  1. 将打字稿添加到我们的 ts 项目
vue add typescript
  1. 配置tsconfig文件支持javascript和typescript模块

    我建议您必须添加

    • allowJS 为 true 以便它允许将 js 文件导入到我们的 ts 模块中。

    • importHelper为真。

    • allowSyntheticDefaultImports 为真,它允许像下面这样的默认导出,我认为这个 属性 编译器选项将帮助您完成当前情况的工作。在我看来,它无法正确地重新编译导入或导出语句。

import TestClass from "resources/ts/helpers";
  1. 在你的项目目录下添加shims-vue.d.tsshim-tsx.d.ts文件,可以让typescript理解*.vue文件和代码风格的JSX语法。如果你想知道更多关于两者之间的区别,你应该阅读这个 .
//shims-vue.d.ts
declare module "*.vue" {
  import Vue from 'vue';
  export default Vue;
}

//shims-tsx.d.ts
import Vue, { VNode } from 'vue';

declare global {
  namespace JSX {
    // tslint:disable no-empty-interface
    interface Element extends VNode {}
    // tslint:disable no-empty-interface
    interface ElementClass extends Vue {}
    interface IntrinsicElements {
      [elem: string]: any;
    }
  }
}

为了让 typescript 读取这两个文件,我们需要将这些文件添加到我们的 tsconfig 的 files 属性 中。

// tsconfig.ts
  "files": [
     "shims-vue.d.ts",
     "shims-tsx.d.ts"
   ] 

您的整个 tsconfig 文件如下所示

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "noImplicitThis": true,
    "jsx": "preserve",
    "moduleResolution": "node",
    "esModuleInterop": true,

 // my recommendation
    "importHelpers": true,
    "allowJS": true,
    "allowSyntheticDefaultImports": true,
 // -------------------------

    "skipLibCheck": true,
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ],
    "baseUrl": ".",
    "paths": {
      "resources/*": [
        "resources/*"
      ]
    }
  },
 // my addition
  "files": [
    "shims-vue.d.ts",
    "shims-tsx.d.ts"
  ] 
 // -------------
  "include": [
    "resources/**/*.ts",
    "resources/**/*.tsx",
    "resources/**/*.vue",
  ],
  "exclude": [
    "node_modules"
  ]
}
  1. 现在,最后一步是配置我们的 webpack encore。 我检查了你的配置,发现了一些你遗漏的配置。
    • 首先,您将 main.js 转换为 main.ts
    • 要让 ts-loader 解析 .vue 文件中的 <script lang="ts"> 块,您需要添加 appendTsSuffixTo 配置。
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');

 // ...
 Encore
     // ...
     .addEntry('main', './resources/main.ts')

     .enableTypeScriptLoader(function (tsConfig){
       tsConfig.appendTsSuffixTo = [/\.vue$/]; 
       tsConfig.appendTsxSuffixTo = [/\.vue$/]; 
     })

     // don't know the reason why you didn't add HtmlWebpackPlugin
     .addPlugin(new HtmlWebpackPlugin({
       template: './src/main.html',
     }))    

如果您按照所有这些步骤操作,我想您不会遇到任何问题。最后,你还需要 @babel/typescript 或许多 typescript 插件来支持项目中的 typescript。