Vue3 Typescript 打破了 webpack encore watcher

Posted

技术标签:

【中文标题】Vue3 Typescript 打破了 webpack encore watcher【英文标题】:Vue3 Typescript breaks the webpack encore watcher 【发布时间】:2021-10-16 19:59:33 【问题描述】:

我有一个 vue3/symfony 项目,我开始实现 typescript,但遇到了一个我无法解决的问题。对于构建资产,我使用的是 webpack encore,当我启动观察程序时,资产编译得很好,但是当我更改 .vue 文件中的任何内容时(甚至添加空格以强制 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 上进行任何类型的更改(甚至是空格),编译将再次成功。

这只发生在 .ts 文件导入到 .vue 文件中。我导入的 .js 或 .vue(带或不带 typescript)文件都很好

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"
  

【问题讨论】:

【参考方案1】:

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

    将打字稿添加到我们的 ts 项目中
vue add typescript

    配置 tsconfig 文件以支持 javascript 和 typescript 模块

    我建议你必须添加

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

    importHelper 为真。

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

import TestClass from "resources/ts/helpers";
    在你的项目目录下添加shims-vue.d.tsshim-tsx.d.ts文件,它将让typescript理解*.vue文件和代码风格的JSX语法。如果您想进一步了解两者之间的区别,请阅读此answer。
//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"
  ]

    现在,最后一步是配置我们的 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 或许多打字稿插件来支持项目中的打字稿。

【讨论】:

感谢您的工作,但是@joachimwedin 先发布了它,他的回答就足够了。尽管您的答案看起来很完整,但为了构建 vue3/symfony + ts 项目,您的某些观点并不是强制性的,如此处所述 (symfony.com/doc/current/frontend/encore/typescript.html) 和 (v3.vuejs.org/guide/…) 这很公平。 :) 这对其他人会有所帮助:)【参考方案2】:

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

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

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

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

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

【讨论】:

是的,这就是问题所在......不太清楚我是怎么错过的,这里说得很清楚(symfony.com/doc/current/frontend/encore/typescript.html)

以上是关于Vue3 Typescript 打破了 webpack encore watcher的主要内容,如果未能解决你的问题,请参考以下文章

打破 10 个 Typescript 编程坏习惯

Vue3 + TypeScript 开发实践总结

Vue3都要上的TypeScript之工程实践

带有 Typescript 的 Vue3 路由器:缺少 RouteConfig

Vue3+TypeScript完整项目上手教程

vue3+typescript上传文件到七牛云