FranckFreiburger / vue3-sfc-loader

Single File Component loader for Vue2 and Vue3. Load .vue files directly from your HTML. No node.js environment, no build step.
MIT License
1.03k stars 116 forks source link

Typescript support and examples #61

Closed patrickelectric closed 5 months ago

patrickelectric commented 3 years ago

Describe the bug

Problems while creating the Options field for loadModule

To Reproduce

<template>
  <component :is="computedComponent" />
</template>

<script lang="ts">
import { loadModule } from "vue3-sfc-loader/src/index"
import { defineComponent, defineAsyncComponent } from "vue"

const options = {
    async getFile(url: string) {
        const res = await fetch(url)
        if ( !res.ok )
            throw Object.assign(new Error(`${res.statusText} ${url}`), { res })
        return await res.text()
    },
    addStyle(textContent: any) {

        const style = Object.assign(document.createElement("style"), { textContent })
        const ref = document.head.getElementsByTagName("style")[0] || null
        document.head.insertBefore(style, ref)
    },
}

export default defineComponent({
    data() {
        return {
            currentComponent: "./DashBoard.vue",
        }
    },
    props: {
        url: {
            type: String,
            required: true
        },
    },
    computed: {
        computedComponent() {
            const currentComponent: String = this.url // the trick is here
            return defineAsyncComponent( () => loadModule(currentComponent, options) )
        }
    },
})
</script>

Expected behavior It should works but it gets:

ERROR in src/components/Page.vue:41:77
TS2345: Argument of type '{ getFile(url: string): Promise<string>; addStyle(textContent: any): void; }' is not assignable to parameter of type 'Options'.
  Type '{ getFile(url: string): Promise<string>; addStyle(textContent: any): void; }' is missing the following properties from type 'Options': pathResolve, getResource
    39 |         computedComponent() {
    40 |             const currentComponent: String = this.url // the trick is here
  > 41 |             return defineAsyncComponent( () => loadModule(currentComponent, options) )
       |                                                                             ^^^^^^^
    42 |         }
    43 |     },
    44 | })

Versions

rafaellehmkuhl commented 3 years ago

Seems that you're missing the moduleCache key on options object.

FranckFreiburger commented 3 years ago

Thanks @rafaellehmkuhl moduleCache is mandatory but is marked as optional in the doc moduleCache? (my bad)

rafaellehmkuhl commented 3 years ago

Looking at the source index.ts now, I don't see a way to pass an options without the pathResolve and getResource, since they are not optional, so TypeScript will throw a type error.

@FranckFreiburger is there any way to use this module with typescript?

patrickelectric commented 3 years ago

moduleCache is mandatory but is marked as optional in the doc moduleCache? (my bad)

Yep, that is why I did like that, but doing so and adding vue does not make it work yet.

<template>
  <component :is="computedComponent" />
</template>

<script lang="ts">
import { loadModule } from "vue3-sfc-loader/src/index"
import Vue from "vue"
import { defineComponent, defineAsyncComponent } from "vue"

const options = {
    moduleCache: {
        vue: Vue
    },
    async getFile(url: string) {
        const res = await fetch(url)
        if ( !res.ok )
            throw Object.assign(new Error(`${res.statusText} ${url}`), { res })
        return await res.text()
    },
    addStyle(textContent: any) {

        const style = Object.assign(document.createElement("style"), { textContent })
        const ref = document.head.getElementsByTagName("style")[0] || null
        document.head.insertBefore(style, ref)
    },
}

export default defineComponent({
    data() {
        return {
            currentComponent: "./DashBoard.vue",
        }
    },
    props: {
        url: {
            type: String,
            required: true
        },
    },
    computed: {
        computedComponent() {
            const currentComponent: String = this.url // the trick is here
            // @ts-ignore
            return defineAsyncComponent( () => loadModule(currentComponent, options) )
        }
    },
})
</script>
ERROR in node_modules/vue3-sfc-loader/src/tools.ts:283:12
TS2532: Object is possibly 'undefined'.
    281 |                       const module = await loadModule(id, options);
    282 |                       if ( module !== undefined )
  > 283 |                               return moduleCache[id] = module;
        |                                      ^^^^^^^^^^^
    284 |               }
    285 | 
    286 |               const { content, type } = await getContent();

ERROR in node_modules/vue3-sfc-loader/src/tools.ts:303:10
TS2532: Object is possibly 'undefined'.
    301 |                       throw new TypeError(`Unable to handle ${ type } files (${ path })`);
    302 | 
  > 303 |               return moduleCache[id] = module;
        |                      ^^^^^^^^^^^
    304 | 
    305 |       })());
    306 | 

ERROR in node_modules/vue3-sfc-loader/src/tools.ts:303:10
TS2322: Type 'ModuleExport | null' is not assignable to type 'ModuleExport | LoadingType<ModuleExport>'.
  Type 'null' is not assignable to type 'ModuleExport | LoadingType<ModuleExport>'.
    301 |                       throw new TypeError(`Unable to handle ${ type } files (${ path })`);
    302 | 
  > 303 |               return moduleCache[id] = module;
        |                      ^^^^^^^^^^^^^^^
    304 | 
    305 |       })());
    306 | 

ERROR in node_modules/vue3-sfc-loader/src/tools.ts:307:16
TS2532: Object is possibly 'undefined'.
    305 |       })());
    306 | 
  > 307 |       return await (moduleCache[id] as LoadingType<ModuleExport>).promise;
        |                     ^^^^^^^^^^^
    308 | }
    309 | 
    310 | 

ERROR in node_modules/vue3-sfc-loader/src/tools.ts:324:14
TS2532: Object is possibly 'undefined'.
    322 | 
    323 |               const { id } = getResource({ refPath, relPath }, options);
  > 324 |               if ( id in moduleCache )
        |                          ^^^^^^^^^^^
    325 |                       return moduleCache[id];
    326 | 
    327 |               throw new Error(`require(${ JSON.stringify(id) }) failed. module not found in moduleCache`);

ERROR in node_modules/vue3-sfc-loader/src/tools.ts:325:11
TS2532: Object is possibly 'undefined'.
    323 |               const { id } = getResource({ refPath, relPath }, options);
    324 |               if ( id in moduleCache )
  > 325 |                       return moduleCache[id];
        |                              ^^^^^^^^^^^
    326 | 
    327 |               throw new Error(`require(${ JSON.stringify(id) }) failed. module not found in moduleCache`);
    328 |       }

ERROR in node_modules/vue3-sfc-loader/src/tools.ts:354:58
TS2345: Argument of type 'Cache | undefined' is not assignable to parameter of type 'Cache'.
  Type 'undefined' is not assignable to type 'Cache'.
    352 |       const { compiledCache } = options;
    353 | 
  > 354 |       const [ depsList, transformedSource ] = await withCache(compiledCache, [ version, source, filename ], async () => {
        |                                                               ^^^^^^^^^^^^^
    355 | 
    356 |               return await transformJSCode(source, moduleSourceType, filename, options);
    357 |       });

ERROR in node_modules/vue3-sfc-loader/src/tools.ts:385:2
TS2322: Type 'undefined' is not assignable to type 'ModuleExport | null'.
    383 |       }
    384 | 
  > 385 |       return undefined;
        |       ^^^^^^^^^^^^^^^^^
    386 | }
    387 | 
FranckFreiburger commented 3 years ago

Loading from sources "vue3-sfc-loader/src/index" will not work. Sources are intended to be processed by webpack before being usable.

patrickelectric commented 3 years ago

Hi @FranckFreiburger, I got it closer using only "vue3-sfc-loader" and adding it to shims file, but it's not working, I'm able to compile it but with a couple of problems: image And when starting yarn:

 WARNING  Compiled with 1 warnings                                                                                                                                         8:49:06 AM

 warning  in ./src/components/Page.vue?vue&type=script&lang=ts

"export 'default' (imported as 'Vue') was not found in 'vue'

  App running at:
  - Local:   http://localhost:8080/ 
  - Network: http://192.168.0.109:8080/

  Note that the development build is not optimized.
  To create a production build, run yarn build.

No issues found.
patrickelectric commented 3 years ago

I believe that the main problem is how to create moduleCache in typescript, It's not possible from what I could try to get Vue, since typescript set is as undefined since the vue module does not export such thing, any tip ?

rafaellehmkuhl commented 3 years ago

Hi @FranckFreiburger, I got it closer using only "vue3-sfc-loader" and adding it to shims file, but it's not working, I'm able to compile it but with a couple of problems: image And when starting yarn:

 WARNING  Compiled with 1 warnings                                                                                                                                         8:49:06 AM

 warning  in ./src/components/Page.vue?vue&type=script&lang=ts

"export 'default' (imported as 'Vue') was not found in 'vue'

  App running at:
  - Local:   http://localhost:8080/ 
  - Network: http://192.168.0.109:8080/

  Note that the development build is not optimized.
  To create a production build, run yarn build.

No issues found.

In fact I've tried here and faced the same problem.

This seems to be a problem with Vue3, since Vue3 does not export Vue instance by default, but only the namespaces (e.g.: createApp).

And yes, the types are also not being found.

ERROR in src/App.vue:9:28
TS7016: Could not find a declaration file for module 'vue3-sfc-loader'. '/home/rafael/Temporary/vue-comp-loader/comp-loader/node_modules/vue3-sfc-loader/dist/vue3-sfc-loader.js' implicitly has an 'any' type.
  Try `npm i --save-dev @types/vue3-sfc-loader` if it exists or add a new declaration (.d.ts) file containing `declare module 'vue3-sfc-loader';`
     7 | <script lang='ts'>
     8 | import Vue from 'vue'
  >  9 | import { loadModule } from 'vue3-sfc-loader'
Toilal commented 3 years ago

The main issue is that webpack doesn't build a proper d.ts file. It should be a single d.ts, referenced through package.json typings field.

We have to find a way to build a single d.ts file matching the dist bundle. Maybe the easier to achieve this is to manually write d.ts file and implement it in TypeScript sources.

973782523 commented 3 years ago

The main issue is that webpack doesn't build a proper d.ts file. It should be a single d.ts, referenced through package.json typings field.

We have to find a way to build a single d.ts file matching the dist bundle. Maybe the easier to achieve this is to manually write d.ts file and implement it in TypeScript sources. I still report an error according to the operation of the appeal, and the error information is similar to yours. Have you solved it later?
Handwritten d.ts? If a file is requested by GET, the load is still out of order, indicating the masked status. Can you be specific?
The console is still having a typeset error

973782523 commented 3 years ago
import Vue from 'vue'
import {defineComponent, defineAsyncComponent} from 'vue';
import {loadModule} from "vue3-sfc-loader/src/index"
import axios from "axios";
const options = {
  moduleCache: {vue: Vue},
  getFile(url: string) {
    return axios.get(url).then(res => {
      return res.data
    })
  },
  addStyle: () => {
  },
}
const LocalComponent = defineAsyncComponent(() => loadModule('/public/myComponent1.vue', options));

I don't know how to start this error

BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default. This is no longer the case. Verify if you need this module and configure a polyfill for it.

If you want to include a polyfill, you need to:

ERROR in node_modules/vue3-sfc-loader/src/index.ts:3:8 TS2307: Cannot find module 'path' or its corresponding type declarations. 1 | import { 2 | posix as Path

3 | } from 'path' | ^^^^^^ 4 | 5 | import { 6 | loadModuleInternal

ERROR in node_modules/vue3-sfc-loader/src/index.ts:167:3 TS2783: 'moduleCache' is specified more than once, so this usage will be overwritten. 165 | 166 | const normalizedOptions = {

167 | moduleCache, | ^^^^^^^^^^^ 168 | pathResolve, 169 | getResource, 170 | ...options,

ERROR in node_modules/vue3-sfc-loader/src/index.ts:168:3 TS2783: 'pathResolve' is specified more than once, so this usage will be overwritten. 166 | const normalizedOptions = { 167 | moduleCache,

168 | pathResolve, | ^^^^^^^^^^^ 169 | getResource, 170 | ...options, 171 | };

ERROR in node_modules/vue3-sfc-loader/src/index.ts:169:3 TS2783: 'getResource' is specified more than once, so this usage will be overwritten. 167 | moduleCache, 168 | pathResolve,

169 | getResource, | ^^^^^^^^^^^ 170 | ...options, 171 | }; 172 |

ERROR in node_modules/vue3-sfc-loader/src/index.ts:173:37 TS2322: Type 'undefined' is not assignable to type 'AbstractPath'. 171 | }; 172 |

173 | return await loadModuleInternal( { refPath: undefined, relPath: path }, normalizedOptions); | ^^^^^^^ 174 | } 175 | 176 | /**

ERROR in node_modules/vue3-sfc-loader/src/tools.ts:9:8 TS7016: Could not find a declaration file for module '@babel/core'. 'E:/caogao/测试模板/vue3-build/node_modules/@babel/core/lib/index.js' implicitly has an 'any' type. Try npm i --save-dev @types/babel__core if it exists or add a new declaration (.d.ts) file containing declare module '@babel/core'; 7 | transformFromAstAsync as babel_transformFromAstAsync, 8 | types as t,

9 | } from '@babel/core'; | ^^^^^^^^^^^^^ 10 | 11 | import { 12 | parse as babel_parse,

ERROR in node_modules/vue3-sfc-loader/src/tools.ts:19:8 TS7016: Could not find a declaration file for module '@babel/code-frame'. 'E:/caogao/测试模板/vue3-build/node_modules/@babel/code-frame/lib/index.js' implicitly has an 'any' type. Try npm i --save-dev @types/babel__code-frame if it exists or add a new declaration (.d.ts) file containing declare module '@babel/code-frame'; 17 | codeFrameColumns, 18 | SourceLocation,

19 | } from '@babel/code-frame'; | ^^^^^^^^^^^^^^^^^^^ 20 | 21 | // @ts-ignore (Could not find a declaration file for module '@babel/plugin-transform-modules-commonjs') 22 | import babelPluginTransformModulesCommonjs from '@babel/plugin-transform-modules-commonjs'

ERROR in node_modules/vue3-sfc-loader/src/tools.ts:31:27 TS2307: Cannot find module 'spark-md5' or its corresponding type declarations. 29 | 30 |

31 | import * as SparkMD5 from 'spark-md5' | ^^^^^^^^^^^ 32 | 33 | import { 34 | Cache,

ERROR in node_modules/vue3-sfc-loader/src/tools.ts:275:32 TS2345: Argument of type 'Promise<ModuleExport | null>' is not assignable to parameter of type 'Promise'. Type 'ModuleExport | null' is not assignable to type 'ModuleExport'. Type 'null' is not assignable to type 'ModuleExport'. 273 | 274 |

275 | moduleCache[id] = new Loading((async () => { | ^^^^^^^^^^^^^^ 276 | | ^ 277 | if ( loadModule ) { | ^ 278 | | ^ 279 | const module = await loadModule(id, options); | ^ 280 | if ( module !== undefined ) | ^ 281 | return moduleCache[id] = module; | ^ 282 | } | ^ 283 | | ^ 284 | const { getContentData, type } = await getContent(); | ^ 285 | | ^ 286 | // note: null module is accepted | ^ 287 | let module : ModuleExport | undefined | null = undefined; | ^ 288 | | ^ 289 | if ( handleModule !== undefined ) | ^ 290 | module = await handleModule(type, getContentData, path, options); | ^ 291 | | ^ 292 | if ( module === undefined ) | ^ 293 | module = await defaultHandleModule(type, getContentData, path, options); | ^ 294 | | ^ 295 | if ( module === undefined ) | ^ 296 | throw new TypeError(Unable to handle ${ type } files (${ path })); | ^ 297 | | ^ 298 | return moduleCache[id] = module; | ^ 299 | | ^ 300 | })()); | ^^^^^^ 301 | 302 | return await (moduleCache[id] as LoadingType).promise; 303 | }

ERROR in node_modules/vue3-sfc-loader/src/tools.ts:298:10 TS2322: Type 'ModuleExport | null' is not assignable to type 'ModuleExport | LoadingType'. Type 'null' is not assignable to type 'ModuleExport | LoadingType'. 296 | throw new TypeError(Unable to handle ${ type } files (${ path })); 297 |

298 | return moduleCache[id] = module; | ^^^^^^^^^^^^^^^ 299 | 300 | })()); 301 |

ERROR in node_modules/vue3-sfc-loader/src/tools.ts:349:58 TS2345: Argument of type 'Cache | undefined' is not assignable to parameter of type 'Cache'. Type 'undefined' is not assignable to type 'Cache'. 347 | const { compiledCache, additionalBabelParserPlugins, additionalBabelPlugins, log } = options; 348 |

349 | const [ depsList, transformedSource ] = await withCache(compiledCache, [ version, source, filename ], async () => { | ^^^^^^^^^^^^^ 350 | 351 | return await transformJSCode(source, moduleSourceType, filename, additionalBabelParserPlugins, additionalBabelPlugins, log); 352 | });

ERROR in node_modules/vue3-sfc-loader/src/tools.ts:380:2 TS2322: Type 'undefined' is not assignable to type 'ModuleExport | null'. 378 | } 379 |

380 | return undefined; | ^^^^^^^^^^^^^^^^^ 381 | } 382 |

ERROR in src/components/HelloWorld.vue:52:90 TS2345: Argument of type '{ moduleCache: { vue: typeof import("E:/caogao/\u6D4B\u8BD5\u6A21\u677F/vue3-build/node_modules/vue/dist/vue"); }; getFile(url: string): Promise; addStyle: () => void; }' is not assignable to parameter of type 'Options'. Type '{ moduleCache: { vue: typeof import("E:/caogao/\u6D4B\u8BD5\u6A21\u677F/vue3-build/node_modules/vue/dist/vue"); }; getFile(url: string): Promise; addStyle: () => void; }' is missing the following properties from type 'O ptions': pathResolve, getResource 50 | // 51 | // }

52 | const LocalComponent = defineAsyncComponent(() => loadModule('/public/myComponent1.vue', options));

FranckFreiburger commented 5 months ago

There seems to be no more activity on this issue. don't hesitate to ask me to reopen the issue if necessary.