├── .prettierrc ├── packages └── vue-rsloader │ ├── src │ ├── reset.d.ts │ ├── plugin.ts │ ├── shim.d.ts │ ├── styleInlineLoader.ts │ ├── exportHelper.ts │ ├── cssModules.ts │ ├── formatError.ts │ ├── util.ts │ ├── hotReload.ts │ ├── compiler.ts │ ├── stylePostLoader.ts │ ├── descriptorCache.ts │ ├── select.ts │ ├── resolveScript.ts │ ├── templateLoader.ts │ ├── pluginRspack.ts │ └── index.ts │ ├── README.md │ ├── tsconfig.test.json │ ├── tsconfig.json │ ├── LICENSE │ └── package.json ├── pnpm-workspace.yaml ├── .gitignore ├── example └── modernjs-builder │ ├── README.md │ ├── src │ ├── assets │ │ ├── rspack.png │ │ └── vue.svg │ ├── main.ts │ ├── env.d.ts │ ├── components │ │ ├── HelloWorld.vue │ │ └── readme.md │ ├── App.vue │ └── style.css │ ├── babel.config.js │ ├── tailwind.config.js │ ├── tsconfig.node.json │ ├── builder │ ├── jsx.mjs │ ├── svg.js │ ├── mdx.mjs │ ├── builder.mjs │ └── vue-builder-plugin.mjs │ ├── tsconfig.json │ └── package.json ├── package.json ├── vercel.json ├── README.zh-CN.md └── README.md /.prettierrc: -------------------------------------------------------------------------------- 1 | semi: false 2 | singleQuote: true 3 | printWidth: 80 4 | -------------------------------------------------------------------------------- /packages/vue-rsloader/src/reset.d.ts: -------------------------------------------------------------------------------- 1 | import '@total-typescript/ts-reset' 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | - 'example/*' 4 | -------------------------------------------------------------------------------- /packages/vue-rsloader/README.md: -------------------------------------------------------------------------------- 1 | Check out https://github.com/skywalker512/vue-rsloader 2 | -------------------------------------------------------------------------------- /packages/vue-rsloader/src/plugin.ts: -------------------------------------------------------------------------------- 1 | import VueLoaderPlugin from './pluginRspack' 2 | 3 | export default VueLoaderPlugin 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | dist-ssr 6 | link.sh 7 | .cache 8 | TODOs.md 9 | coverage 10 | .vscode 11 | -------------------------------------------------------------------------------- /packages/vue-rsloader/src/shim.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'merge-source-map' { 2 | export default function merge(inMap: any, outMap: any): any 3 | } 4 | -------------------------------------------------------------------------------- /example/modernjs-builder/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + TypeScript + Rspack + Modernjs Builder 2 | 3 | Fork from vite template 4 | 5 | more: tailwind、scss ... 6 | -------------------------------------------------------------------------------- /example/modernjs-builder/src/assets/rspack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skywalker512/vue-rsloader/HEAD/example/modernjs-builder/src/assets/rspack.png -------------------------------------------------------------------------------- /example/modernjs-builder/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-typescript'], 3 | plugins: ['@vue/babel-plugin-jsx'], 4 | } 5 | -------------------------------------------------------------------------------- /example/modernjs-builder/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import './style.css' 3 | import App from './App.vue' 4 | 5 | createApp(App).mount('#app') 6 | -------------------------------------------------------------------------------- /example/modernjs-builder/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./src/**/*.{vue,js,ts,jsx,tsx}'], 4 | corePlugins: { 5 | preflight: false, 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-rsloader-private", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "pnpm run -r build", 7 | "dev": "pnpm run -r --parallel dev" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/vue-rsloader/src/styleInlineLoader.ts: -------------------------------------------------------------------------------- 1 | import webpack = require('webpack') 2 | 3 | const StyleInineLoader: webpack.loader.Loader = function (source) { 4 | // TODO minify this? 5 | return `export default ${JSON.stringify(source)}` 6 | } 7 | 8 | export default StyleInineLoader 9 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildCommand": "pnpm build", 3 | "outputDirectory": "./example/modernjs-builder/dist", 4 | "installCommand": "pnpm i", 5 | "rewrites": [ 6 | { 7 | "source": "/(.*)", 8 | "destination": "/html/index/index.html" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/vue-rsloader/src/exportHelper.ts: -------------------------------------------------------------------------------- 1 | // runtime helper for setting properties on components 2 | // in a tree-shakable way 3 | export default (sfc: any, props: [string, string][]) => { 4 | const target = sfc.__vccOpts || sfc 5 | for (const [key, val] of props) { 6 | target[key] = val 7 | } 8 | return target 9 | } 10 | -------------------------------------------------------------------------------- /packages/vue-rsloader/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "target": "ESNext", 7 | "allowSyntheticDefaultImports": true, 8 | "checkJs": true, 9 | "allowJs": true, 10 | "outDir": "./dist" 11 | }, 12 | "include": ["./test"] 13 | } 14 | -------------------------------------------------------------------------------- /example/modernjs-builder/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "target": "ESNext", 7 | "allowSyntheticDefaultImports": true, 8 | "checkJs": true, 9 | "allowJs": true, 10 | "outDir": "./dist" 11 | }, 12 | "include": ["./builder", "*.js"] 13 | } 14 | -------------------------------------------------------------------------------- /example/modernjs-builder/src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/modernjs-builder/builder/jsx.mjs: -------------------------------------------------------------------------------- 1 | import Module from 'node:module' 2 | 3 | const require = Module.createRequire(import.meta.url) 4 | /** 5 | * @returns {import('@modern-js/builder-shared').DefaultBuilderPlugin} 6 | */ 7 | export const vueJsxPlugin = () => ({ 8 | name: 'vue-jsx-plugin', 9 | setup(api) { 10 | api.modifyBundlerChain(async (chain) => { 11 | const rule = chain.module.rule('vue-jsx').test(/\.(t|j)sx$/) 12 | rule.use('vue-jsx').loader(require.resolve('babel-loader')).end() 13 | }) 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /example/modernjs-builder/builder/svg.js: -------------------------------------------------------------------------------- 1 | const { compileTemplate } = require('vue/compiler-sfc') 2 | 3 | const VueSvgLoader = function (source) { 4 | source = String(source) 5 | 6 | const loaderContext = this 7 | 8 | const { code } = compileTemplate({ 9 | id: loaderContext.resourcePath, 10 | source, 11 | filename: loaderContext.resourcePath, 12 | transformAssetUrls: false, 13 | }) 14 | 15 | loaderContext.callback(null, `${code}\nexport default { render: render }`) 16 | } 17 | 18 | module.exports = VueSvgLoader 19 | -------------------------------------------------------------------------------- /example/modernjs-builder/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "resolveJsonModule": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "lib": ["ESNext", "DOM"], 13 | "skipLibCheck": true, 14 | "noEmit": true 15 | }, 16 | "include": ["src"], 17 | "references": [{ "path": "./tsconfig.node.json" }] 18 | } 19 | -------------------------------------------------------------------------------- /example/modernjs-builder/src/env.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | import { FunctionalComponent, SVGAttributes } from 'vue' 3 | const src: FunctionalComponent 4 | export default src 5 | } 6 | 7 | declare module '*.mdx' { 8 | import { FunctionalComponent, SVGAttributes } from 'vue' 9 | const src: FunctionalComponent 10 | export default src 11 | } 12 | 13 | declare module '*.md' { 14 | import { FunctionalComponent, SVGAttributes } from 'vue' 15 | const src: FunctionalComponent 16 | export default src 17 | } 18 | -------------------------------------------------------------------------------- /packages/vue-rsloader/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "dist", 5 | "sourceMap": false, 6 | "target": "ES2018", 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "esModuleInterop": true, 10 | "declaration": true, 11 | "allowJs": false, 12 | "noUnusedLocals": true, 13 | "strictNullChecks": true, 14 | "noImplicitAny": true, 15 | "removeComments": false, 16 | "skipLibCheck": true, 17 | "lib": ["ESNext"] 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.test.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /packages/vue-rsloader/src/cssModules.ts: -------------------------------------------------------------------------------- 1 | export function genCSSModulesCode( 2 | id: string, 3 | index: number, 4 | request: string, 5 | moduleName: string | boolean, 6 | needsHotReload: boolean 7 | ): string { 8 | const styleVar = `style${index}` 9 | let code = `\nimport ${styleVar} from ${request}` 10 | 11 | // inject variable 12 | const name = typeof moduleName === 'string' ? moduleName : '$style' 13 | code += `\ncssModules["${name}"] = ${styleVar}` 14 | 15 | if (needsHotReload) { 16 | code += ` 17 | if (module.hot) { 18 | module.hot.accept(${request}, () => { 19 | cssModules["${name}"] = ${styleVar} 20 | __VUE_HMR_RUNTIME__.rerender("${id}") 21 | }) 22 | }` 23 | } 24 | 25 | return code 26 | } 27 | -------------------------------------------------------------------------------- /packages/vue-rsloader/src/formatError.ts: -------------------------------------------------------------------------------- 1 | import type { CompilerError } from 'vue/compiler-sfc' 2 | import { generateCodeFrame } from 'vue/compiler-sfc' 3 | import chalk from 'chalk' 4 | 5 | export function formatError( 6 | err: SyntaxError | CompilerError, 7 | source: string, 8 | file: string 9 | ) { 10 | const loc = (err as CompilerError).loc 11 | if (!loc) { 12 | return 13 | } 14 | const locString = `:${loc.start.line}:${loc.start.column}` 15 | const filePath = chalk.gray(`at ${file}${locString}`) 16 | const codeframe = generateCodeFrame(source, loc.start.offset, loc.end.offset) 17 | err.message = `\n${chalk.red( 18 | `VueCompilerError: ${err.message}` 19 | )}\n${filePath}\n${chalk.yellow(codeframe)}\n` 20 | } 21 | -------------------------------------------------------------------------------- /packages/vue-rsloader/src/util.ts: -------------------------------------------------------------------------------- 1 | import type { SFCDescriptor, CompilerOptions } from 'vue/compiler-sfc' 2 | import type { VueLoaderOptions } from '.' 3 | 4 | export function resolveTemplateTSOptions( 5 | descriptor: SFCDescriptor, 6 | options: VueLoaderOptions 7 | ): CompilerOptions | null { 8 | if (options.enableTsInTemplate === false) return null 9 | 10 | const lang = descriptor.script?.lang || descriptor.scriptSetup?.lang 11 | const isTS = !!(lang && /tsx?$/.test(lang)) 12 | let expressionPlugins = options?.compilerOptions?.expressionPlugins || [] 13 | if (isTS && !expressionPlugins.includes('typescript')) { 14 | expressionPlugins = [...expressionPlugins, 'typescript'] 15 | } 16 | return { 17 | isTS, 18 | expressionPlugins, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/vue-rsloader/src/hotReload.ts: -------------------------------------------------------------------------------- 1 | // __VUE_HMR_RUNTIME__ is injected to global scope by @vue/runtime-core 2 | 3 | export function genHotReloadCode( 4 | id: string, 5 | templateRequest: string | undefined 6 | ): string { 7 | return ` 8 | /* hot reload */ 9 | if (module.hot) { 10 | __exports__.__hmrId = "${id}" 11 | const api = __VUE_HMR_RUNTIME__ 12 | module.hot.accept() 13 | if (!api.createRecord('${id}', __exports__)) { 14 | api.reload('${id}', __exports__) 15 | } 16 | ${templateRequest ? genTemplateHotReloadCode(id, templateRequest) : ''} 17 | } 18 | ` 19 | } 20 | 21 | function genTemplateHotReloadCode(id: string, request: string) { 22 | return ` 23 | module.hot.accept(${request}, () => { 24 | api.rerender('${id}', render) 25 | }) 26 | ` 27 | } 28 | -------------------------------------------------------------------------------- /packages/vue-rsloader/src/compiler.ts: -------------------------------------------------------------------------------- 1 | // extend the descriptor so we can store the scopeId on it 2 | declare module 'vue/compiler-sfc' { 3 | interface SFCDescriptor { 4 | id: string 5 | } 6 | } 7 | 8 | import * as _compiler from 'vue/compiler-sfc' 9 | 10 | export let compiler: typeof _compiler 11 | 12 | try { 13 | // Vue 3.2.13+ ships the SFC compiler directly under the `vue` package 14 | // making it no longer necessary to have @vue/compiler-sfc separately installed. 15 | compiler = require('vue/compiler-sfc') 16 | } catch (e) { 17 | try { 18 | compiler = require('@vue/compiler-sfc') 19 | } catch (e) { 20 | throw new Error( 21 | `@vitejs/plugin-vue requires vue (>=3.2.13) or @vue/compiler-sfc ` + 22 | `to be present in the dependency tree.` 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example/modernjs-builder/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | {{ msg }} 13 | 14 | 15 | count is {{ count }} 16 | 17 | Edit 18 | components/HelloWorld.vue to test HMR 19 | 20 | 21 | 22 | 23 | Vue Rspack Loader Github 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 37 | -------------------------------------------------------------------------------- /example/modernjs-builder/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "modernjs-builder", 3 | "private": true, 4 | "scripts": { 5 | "build": "node builder/builder.mjs build", 6 | "dev": "node builder/builder.mjs" 7 | }, 8 | "keywords": [], 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "@babel/core": "^7.21.3", 13 | "@babel/preset-typescript": "^7.21.0", 14 | "@mdx-js/loader": "^2.3.0", 15 | "@modern-js/builder": "^2.9.0", 16 | "@modern-js/builder-rspack-provider": "^2.9.0", 17 | "@modern-js/builder-shared": "^2.9.0", 18 | "@vue/babel-plugin-jsx": "^1.1.1", 19 | "babel-loader": "^9.1.2", 20 | "rehype-highlight": "^6.0.0", 21 | "tailwindcss": "^3.2.7", 22 | "vue-rsloader": "workspace:*", 23 | "webpack": "^5.76.2" 24 | }, 25 | "dependencies": { 26 | "@mdx-js/vue": "^2.3.0", 27 | "vue": "^3.2.47" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/modernjs-builder/src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 34 | -------------------------------------------------------------------------------- /example/modernjs-builder/builder/mdx.mjs: -------------------------------------------------------------------------------- 1 | import Module from 'node:module' 2 | import rehypeHighlight from 'rehype-highlight' 3 | const require = Module.createRequire(import.meta.url) 4 | /** 5 | * @returns {import('@modern-js/builder-shared').DefaultBuilderPlugin} 6 | */ 7 | export const vueMdxPlugin = () => ({ 8 | name: 'vue-mdx-plugin', 9 | setup(api) { 10 | api.modifyBundlerChain(async (chain) => { 11 | /** @type {import('@mdx-js/loader').Options} */ 12 | const options = { 13 | providerImportSource: '@mdx-js/vue', 14 | jsx: true, 15 | rehypePlugins: [rehypeHighlight], 16 | } 17 | 18 | const rule = chain.module.rule('vue-mdx').test(/\.mdx?$/) 19 | rule 20 | .use('vue-jsx-mdx') 21 | .loader(require.resolve('babel-loader')) 22 | .end() 23 | .use('vue-mdx') 24 | .loader(require.resolve('@mdx-js/loader')) 25 | .options(options) 26 | .end() 27 | }) 28 | }, 29 | }) 30 | -------------------------------------------------------------------------------- /packages/vue-rsloader/src/stylePostLoader.ts: -------------------------------------------------------------------------------- 1 | import * as qs from 'querystring' 2 | import webpack = require('webpack') 3 | import { compileStyle } from 'vue/compiler-sfc' 4 | 5 | // This is a post loader that handles scoped CSS transforms. 6 | // Injected right before css-loader by the global pitcher (../pitch.js) 7 | // for any
17 | Edit 18 | components/HelloWorld.vue to test HMR 19 |
components/HelloWorld.vue