xbx преди 1 месец
ревизия
6e46cf1581
променени са 26 файла, в които са добавени 5627 реда и са изтрити 0 реда
  1. 1 0
      .env
  2. 7 0
      .env.development
  3. 7 0
      .env.production
  4. 14 0
      .gitignore
  5. 4 0
      .prettierignore
  6. 3 0
      .prettierrc
  7. 23 0
      README.md
  8. 23 0
      eslint.config.mjs
  9. 5 0
      gloab.d.ts
  10. 11 0
      index.html
  11. 38 0
      package.json
  12. 46 0
      plugin/getEnv.ts
  13. 75 0
      plugin/script-attr-plugin.js
  14. 4903 0
      pnpm-lock.yaml
  15. 160 0
      rspack.base.config.ts
  16. 6 0
      rspack.config.ts
  17. 32 0
      rspack.dev.config.ts
  18. 68 0
      rspack.prod.config.ts
  19. 35 0
      src/App.vue
  20. 1 0
      src/assets/rspack.svg
  21. 1 0
      src/assets/vue.svg
  22. 36 0
      src/components/HelloWorld.vue
  23. 7 0
      src/main.ts
  24. 5 0
      src/shims-vue.d.ts
  25. 90 0
      src/style.css
  26. 26 0
      tsconfig.json

+ 1 - 0
.env

@@ -0,0 +1 @@
+VUE_APP_TITLE= RSPACK 测试

+ 7 - 0
.env.development

@@ -0,0 +1,7 @@
+NODE_ENV = development
+
+VUE_OPTIONS_SOURCE_MAP = false
+
+VUE_OPTIONS_BASE_PATH = '/'
+
+VUE_OPTIONS_OUT_DIR = 'dist'

+ 7 - 0
.env.production

@@ -0,0 +1,7 @@
+NODE_ENV = prodction
+
+VUE_OPTIONS_SOURCE_MAP = true
+
+VUE_OPTIONS_BASE_PATH = '/'
+
+VUE_OPTIONS_OUT_DIR = 'dist'

+ 14 - 0
.gitignore

@@ -0,0 +1,14 @@
+# Local
+.DS_Store
+*.local
+*.log*
+
+# Dist
+node_modules
+dist/
+.history
+
+# IDE
+.vscode/*
+!.vscode/extensions.json
+.idea

+ 4 - 0
.prettierignore

@@ -0,0 +1,4 @@
+# Lock files
+package-lock.json
+pnpm-lock.yaml
+yarn.lock

+ 3 - 0
.prettierrc

@@ -0,0 +1,3 @@
+{
+  "singleQuote": true
+}

+ 23 - 0
README.md

@@ -0,0 +1,23 @@
+# Rspack project
+
+## Setup
+
+Install the dependencies:
+
+```bash
+npm install
+```
+
+## Get started
+
+Start the dev server:
+
+```bash
+npm run dev
+```
+
+Build the app for production:
+
+```bash
+npm run build
+```

+ 23 - 0
eslint.config.mjs

@@ -0,0 +1,23 @@
+import {
+  defineConfigWithVueTs,
+  vueTsConfigs,
+} from '@vue/eslint-config-typescript';
+import pluginVue from 'eslint-plugin-vue';
+import { globalIgnores } from 'eslint/config';
+import globals from 'globals';
+
+// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
+// import { configureVueProject } from '@vue/eslint-config-typescript'
+// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
+// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup
+
+export default defineConfigWithVueTs(
+  {
+    name: 'app/files-to-lint',
+    files: ['**/*.{ts,mts,tsx,vue}'],
+  },
+  globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
+  { languageOptions: { globals: globals.browser } },
+  pluginVue.configs['flat/essential'],
+  vueTsConfigs.recommended,
+);

+ 5 - 0
gloab.d.ts

@@ -0,0 +1,5 @@
+export {};
+
+declare global {
+    
+}

+ 11 - 0
index.html

@@ -0,0 +1,11 @@
+<!doctype html>
+<html>
+	<head>
+		<meta charset="UTF-8" />
+		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+		<title>Vue</title>
+	</head>
+	<body>
+		<div id="app"></div>
+	</body>
+</html>

+ 38 - 0
package.json

@@ -0,0 +1,38 @@
+{
+  "name": "rspack-project",
+  "version": "1.0.0",
+  "private": true,
+  "scripts": {
+    "build": "cross-env NODE_ENV=production rspack --mode=production  build ",
+    "dev": "cross-env NODE_ENV=development rspack --mode=development  dev",
+    "format": "prettier --write .",
+    "lint": "eslint .",
+    "preview": "rspack preview"
+  },
+  "dependencies": {
+    "axios": "^1.9.0",
+    "vue": "^3.5.13",
+    "vue-router": "^4.5.1"
+  },
+  "devDependencies": {
+    "@rspack/cli": "^1.3.10",
+    "@rspack/core": "^1.3.10",
+    "@types/node": "^22.15.19",
+    "@vue/eslint-config-typescript": "^14.5.0",
+    "cross-env": "^7.0.3",
+    "css-loader": "^7.1.2",
+    "dotenv": "^16.5.0",
+    "eslint": "^9.23.0",
+    "eslint-plugin-vue": "^10.0.0",
+    "globals": "^16.0.0",
+    "prettier": "^3.5.3",
+    "sass-embedded": "^1.89.0",
+    "sass-loader": "^16.0.5",
+    "style-loader": "^4.0.0",
+    "ts-node": "^10.9.2",
+    "typescript": "^5.8.3",
+    "typescript-eslint": "^8.29.0",
+    "unplugin-auto-import": "^19.2.0",
+    "vue-loader": "^17.4.2"
+  }
+}

+ 46 - 0
plugin/getEnv.ts

@@ -0,0 +1,46 @@
+import path from 'path';
+import fs from 'fs';
+import dotenv from 'dotenv';
+
+interface EnvConfig {
+  [key: string]: string;
+}
+
+//读取env 文件配置
+const loadEnv = () => {
+  const envRoot = path.resolve(process.cwd());
+  const mode = getMode();
+
+  // 加载默认环境变量
+  const envFilePath = path.resolve(envRoot, `./env`);
+  if (fs.existsSync(envFilePath)) {
+    dotenv.config({ path: envFilePath });
+  }
+
+  //加载特定环境变量
+  const envModeFilePath = path.resolve(envRoot, `./env/${mode}`);
+  if (fs.existsSync(envModeFilePath)) {
+    dotenv.config({ path: envModeFilePath });
+  }
+};
+
+//获取指令参数
+const getMode = () => {
+  const args = process.argv
+    .find((item) => item.startsWith('--mode'))
+    ?.split('=')[1];
+
+  if (args) return args;
+  const mode = process.env.NODE_ENV;
+  return mode || 'development';
+};
+
+const isDev = () => {
+  return getMode() === 'development';
+};
+
+const isProd = () => {
+  return getMode() === 'production';
+};
+
+export { loadEnv, getMode, isDev, isProd };

+ 75 - 0
plugin/script-attr-plugin.js

@@ -0,0 +1,75 @@
+
+import { rspack } from '@rspack/core';
+
+const hasADDPrefetch = []
+let runtimeContent = ''
+
+class ScriptAddAttributePlugin {
+    constructor(options) {
+        this.options = options;
+    }
+    apply(compiler) {
+        compiler.hooks.compilation.tap('AddAttributePlugin', compilation => {
+            rspack.HtmlRspackPlugin.getCompilationHooks(
+                compilation,
+            ).alterAssetTags.tapPromise('AddAttributePlugin', async pluginArgs => {
+                pluginArgs.assetTags.scripts = pluginArgs.assetTags.scripts.map(tag => {
+                    if (tag.attributes.src && tag.attributes.src.includes('runtime')) {
+                        runtimeContent = compilation.assets[tag.attributes.src] ? compilation.assets[tag.attributes.src].source() : '';
+                        return tag;
+                    }
+                    if (tag.tagName === 'script') {
+                        tag.attributes.prefetch = true;
+                    }
+                    if (tag.tagName === 'link') {
+                        tag.attributes.prefetch = true;
+                    }
+                    hasADDPrefetch.push(tag.attributes.src)
+                    return tag;
+                }).filter(tag => !tag.attributes.src.includes('runtime'));
+            })
+        });
+    }
+}
+
+class InjectContentPlugin {
+    constructor(options) {
+        this.options = options;
+    }
+    apply(compiler) {
+        compiler.hooks.compilation.tap('InjectContentPlugin', compilation => {
+            rspack.HtmlRspackPlugin.getCompilationHooks(
+                compilation,
+            ).afterTemplateExecution.tapPromise(
+              'InjectContentPlugin',
+                async pluginArgs => {
+                    const allAssets = compilation.getAssets();
+                    const jsFiles = allAssets
+                        .filter(asset => asset.name.endsWith('.js'))
+                        .map(asset => asset.name);
+                    const notAddedFiles = jsFiles.filter(file => !hasADDPrefetch.includes(file) && !file.includes('runtime'));
+                    const scriptTags = notAddedFiles.map(file => `<script prefetch src="${file}"></script>`).join('\n');
+                    if (runtimeContent) {
+                        pluginArgs.html = pluginArgs.html.replace('</body>', `<script>${runtimeContent}</script></body>`);
+                        const runtimeFiles = allAssets
+                            .filter(asset => asset.name.includes('runtime'))
+                            .map(asset => asset.name);
+                        runtimeFiles.forEach(filename => {
+                            // 从compilation中删除runtime文件
+                            compilation.deleteAsset(filename);
+                        });
+                    }
+
+
+                    pluginArgs.html = pluginArgs.html.replace(
+                        '</body>',
+                        `${scriptTags}\n</body>`
+                    );
+                },
+            );
+        });
+    }
+}
+
+
+export { ScriptAddAttributePlugin, InjectContentPlugin }

Файловите разлики са ограничени, защото са твърде много
+ 4903 - 0
pnpm-lock.yaml


+ 160 - 0
rspack.base.config.ts

@@ -0,0 +1,160 @@
+import { type RspackPluginFunction, rspack } from '@rspack/core';
+import { VueLoaderPlugin } from 'vue-loader';
+import path from 'path';
+import { isProd } from './plugin/getEnv';
+import sassEmbedded from 'sass-embedded';
+
+// 目标浏览器配置
+const targets = ['last 2 versions', '> 0.2%', 'not dead', 'Firefox ESR'];
+
+// 基础配置
+export const baseConfig = {
+  entry: {
+    main: './src/main.ts',
+  },
+  resolve: {
+    extensions: ['...', '.ts', '.vue'],
+    alias: {
+      '@': path.resolve(__dirname, './src'),
+    },
+  },
+  module: {
+    rules: [
+      {
+        test: /\.vue$/,
+        loader: 'vue-loader',
+        options: {
+          experimentalInlineMatchResource: true,
+        },
+      },
+      {
+        test: /\.(js|ts)$/,
+        use: [
+          {
+            loader: 'builtin:swc-loader',
+            options: {
+              jsc: {
+                parser: {
+                  syntax: 'typescript',
+                },
+              },
+              env: { targets },
+            },
+          },
+        ],
+      },
+      {
+        test: /\.jsx$/,
+        use: {
+          loader: 'builtin:swc-loader',
+          options: {
+            jsc: {
+              parser: {
+                syntax: 'ecmascript',
+                jsx: true,
+              },
+            },
+          },
+        },
+        type: 'javascript/auto',
+      },
+      {
+        test: /\.tsx$/,
+        use: {
+          loader: 'builtin:swc-loader',
+          options: {
+            jsc: {
+              parser: {
+                syntax: 'typescript',
+                tsx: true,
+              },
+            },
+          },
+        },
+        type: 'javascript/auto',
+      },
+      {
+        test: /\.svg/,
+        type: 'asset/resource',
+      },
+      {
+        test: /\.(png|jpe?g|gif)$/i,
+        type: 'asset/resource',
+      },
+      {
+        oneOf: [
+          {
+            test: /\.css$/i,
+            use: [
+              isProd() ? rspack.CssExtractRspackPlugin.loader : 'style-loader',
+              'css-loader',
+            ],
+            type: 'javascript/auto',
+          },
+
+          {
+            test: /\.(sass|scss)$/,
+            use: [
+              isProd() ? rspack.CssExtractRspackPlugin.loader : 'style-loader',
+              'css-loader',
+              {
+                loader: 'sass-loader',
+                options: {
+                  api: 'modern-compiler',
+                  implementation: sassEmbedded,
+                },
+              },
+            ],
+            type: 'javascript/auto',
+          },
+        ],
+      },
+    ],
+  },
+  plugins: [
+    new rspack.HtmlRspackPlugin({
+      template: './index.html',
+    }),
+    require('unplugin-auto-import/rspack').default({
+      include: [
+        /\.[tj]sx?$/, // .ts, .tsx, .js, .jsx
+        /\.vue$/,
+        /\.vue\?vue/, // .vue
+        /\.md$/, // .md
+      ],
+      imports: [
+        'vue',
+        'vue-router',
+        // 可额外添加需要 autoImport 的组件
+        {
+          // '@/hooks/web/useI18n': ['useI18n'],
+        },
+      ],
+      dts: 'src/types/auto-imports.d.ts',
+      // resolvers: [ElementPlusResolver()],
+      eslintrc: {
+        enabled: false, // Default `false`
+        filepath: './.eslintrc-auto-import.json', // Default `./.eslintrc-auto-import.json`
+        globalsPropValue: true, // Default `true`, (true | false | 'readonly' | 'readable' | 'writable' | 'writeable')
+      },
+    }),
+    new rspack.DefinePlugin({
+      // __VUE_OPTIONS_API__: true,
+      // __VUE_PROD_DEVTOOLS__: false,
+      API_BASE_URL: JSON.stringify(process.env.API_BASE_URL),
+      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
+    }),
+    new VueLoaderPlugin() as RspackPluginFunction,
+  ],
+  optimization: {
+    minimizer: [
+      new rspack.SwcJsMinimizerRspackPlugin(),
+      new rspack.LightningCssMinimizerRspackPlugin({
+        minimizerOptions: { targets },
+      }),
+    ],
+  },
+  experiments: {
+    css: true,
+  },
+};

+ 6 - 0
rspack.config.ts

@@ -0,0 +1,6 @@
+import { defineConfig } from '@rspack/cli';
+import { isDev } from './plugin/getEnv';
+import devConfig from './rspack.dev.config';
+import prodConfig from './rspack.prod.config';
+
+export default defineConfig(isDev() ? devConfig : prodConfig);

+ 32 - 0
rspack.dev.config.ts

@@ -0,0 +1,32 @@
+import { defineConfig } from '@rspack/cli';
+
+import { baseConfig } from './rspack.base.config';
+
+export default defineConfig({
+  ...baseConfig,
+  mode: 'development',
+  entry: {
+    main: './src/main.ts',
+  },
+  devtool: 'eval-cheap-module-source-map',
+  devServer: {
+    hot: true,
+    port: 8089,
+    open: true,
+    historyApiFallback: true,
+    proxy: [
+      {
+        context: ['/api'],
+        target: 'http://localhost:3000',
+        changeOrigin: true,
+        pathRewrite: {
+          '^/api': '',
+        },
+      },
+    ],
+  },
+
+  experiments: {
+    css: true,
+  },
+});

+ 68 - 0
rspack.prod.config.ts

@@ -0,0 +1,68 @@
+import { defineConfig } from '@rspack/cli';
+import { rspack } from '@rspack/core';
+import { baseConfig } from './rspack.base.config';
+
+export default defineConfig({
+  ...baseConfig,
+  mode: 'production',
+  entry: {
+    main: './src/main.ts',
+  },
+  devtool: false,
+  output: {
+    clean: true,
+    filename: '[name].[contenthash].js',
+    chunkFilename: '[name].[contenthash].js',
+  },
+  optimization: {
+    minimize: true,
+    minimizer: [
+      new rspack.SwcJsMinimizerRspackPlugin(),
+      new rspack.LightningCssMinimizerRspackPlugin(),
+    ],
+    splitChunks: {
+      chunks: 'async',
+      minChunks: 1,
+      minSize: 2000,
+      maxAsyncRequests: 30,
+      maxInitialRequests: 30,
+      cacheGroups: {
+        'vue-router': {
+          name: 'vue-router',
+          test: /[\\/]node_modules[\\/]vue-router[\\/]/,
+          priority: 120,
+          chunks: 'all',
+          reuseExistingChunk: true,
+        },
+
+        vue: {
+          name: 'vue',
+          test: /[\\/]node_modules[\\/]vue[\\/]/,
+          priority: 200,
+          chunks: 'all',
+          reuseExistingChunk: true,
+        },
+        axios: {
+          name: 'axios',
+          test: /[\\/]node_modules[\\/]axios[\\/]/,
+          priority: 9,
+          chunks: 'all',
+          reuseExistingChunk: true,
+        },
+        defaultVendors: {
+          test: /[\\/]node_modules[\\/]/,
+          priority: -10,
+          reuseExistingChunk: true,
+        },
+        default: {
+          minChunks: 2,
+          priority: -20,
+          reuseExistingChunk: true,
+        },
+      },
+    },
+  },
+  experiments: {
+    css: true,
+  },
+});

+ 35 - 0
src/App.vue

@@ -0,0 +1,35 @@
+<script setup lang="ts">
+import { ref } from "vue";
+import HelloWorld from "./components/HelloWorld.vue";
+
+const title = ref<string>("Rspack + Vue");
+</script>
+
+<template>
+	<div>
+		<a href="https://www.rspack.dev/" target="_blank">
+			<img src="./assets/rspack.svg" class="logo" alt="Rspack logo" />
+		</a>
+		<a href="https://vuejs.org/" target="_blank">
+			<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
+		</a>
+	</div>
+	<HelloWorld :msg="title" />
+</template>
+
+<style scoped>
+.logo {
+	height: 6em;
+	padding: 1.5em;
+	will-change: filter;
+	transition: filter 300ms;
+}
+
+.logo:hover {
+	filter: drop-shadow(0 0 2em #646cffaa);
+}
+
+.logo.vue:hover {
+	filter: drop-shadow(0 0 2em #42b883aa);
+}
+</style>

Файловите разлики са ограничени, защото са твърде много
+ 1 - 0
src/assets/rspack.svg


+ 1 - 0
src/assets/vue.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

+ 36 - 0
src/components/HelloWorld.vue

@@ -0,0 +1,36 @@
+<script setup lang="ts">
+import { ref } from "vue";
+
+defineProps({
+	msg: {
+		type: String
+	}
+});
+const count = ref<number>(0);
+</script>
+
+<template>
+	<h1>{{ msg }}</h1>
+
+	<div class="card">
+		<button type="button" @click="count++">count is {{ count }}</button>
+		<p>
+			Edit
+			<code>components/HelloWorld.vue</code> to test HMR
+		</p>
+	</div>
+
+	<p>Check out Rspack which support Vue</p>
+	<p>
+		Install
+		<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
+		in your IDE for a better DX
+	</p>
+	<p class="read-the-docs">Click on the Rspack and Vue logos to learn more</p>
+</template>
+
+<style scoped>
+.read-the-docs {
+	color: #888;
+}
+</style>

+ 7 - 0
src/main.ts

@@ -0,0 +1,7 @@
+import "./style.css";
+import { createApp } from "vue";
+import App from "./App.vue";
+
+
+
+createApp(App).mount("#app");

+ 5 - 0
src/shims-vue.d.ts

@@ -0,0 +1,5 @@
+declare module "*.vue" {
+	import type { DefineComponent } from "vue";
+	const component: DefineComponent<{}, {}, any>;
+	export default component;
+}

+ 90 - 0
src/style.css

@@ -0,0 +1,90 @@
+:root {
+  font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
+  font-size: 16px;
+  line-height: 24px;
+  font-weight: 400;
+
+  color-scheme: light dark;
+  color: rgba(255, 255, 255, 0.87);
+  background-color: #242424;
+
+  font-synthesis: none;
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-text-size-adjust: 100%;
+}
+
+a {
+  font-weight: 500;
+  color: #646cff;
+  text-decoration: inherit;
+}
+a:hover {
+  color: #535bf2;
+}
+
+a {
+  font-weight: 500;
+  color: #646cff;
+  text-decoration: inherit;
+}
+a:hover {
+  color: #535bf2;
+}
+
+body {
+  margin: 0;
+  display: flex;
+  place-items: center;
+  min-width: 320px;
+  min-height: 100vh;
+}
+
+h1 {
+  font-size: 3.2em;
+  line-height: 1.1;
+}
+
+button {
+  border-radius: 8px;
+  border: 1px solid transparent;
+  padding: 0.6em 1.2em;
+  font-size: 1em;
+  font-weight: 500;
+  font-family: inherit;
+  background-color: #1a1a1a;
+  cursor: pointer;
+  transition: border-color 0.25s;
+}
+button:hover {
+  border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+  outline: 4px auto -webkit-focus-ring-color;
+}
+
+.card {
+  padding: 2em;
+}
+
+#app {
+  max-width: 1280px;
+  margin: 0 auto;
+  padding: 2rem;
+  text-align: center;
+}
+
+@media (prefers-color-scheme: light) {
+  :root {
+    color: #213547;
+    background-color: #ffffff;
+  }
+  a:hover {
+    color: #747bff;
+  }
+  button {
+    background-color: #f9f9f9;
+  }
+}

+ 26 - 0
tsconfig.json

@@ -0,0 +1,26 @@
+{
+  "compilerOptions": {
+    "target": "ES2020",
+    "lib": ["DOM", "ES2020"],
+    "module": "ESNext",
+    "jsx": "preserve",
+    "jsxImportSource": "vue",
+    "strict": true,
+    "noEmit": true,
+    "skipLibCheck": true,
+    "isolatedModules": true,
+    "resolveJsonModule": true,
+    "moduleResolution": "bundler",
+    "useDefineForClassFields": true,
+    "allowImportingTsExtensions": true
+  },
+  "include": ["src"],
+  "ts-node": {
+    "compilerOptions": {
+      "module": "CommonJS",
+      "esModuleInterop": true, // 启用实验性的 ECMAScript 模块互操作性,方便导入 CommonJS 模块
+      "skipLibCheck": true, // 跳过对库文件的类型检查,可以加快编译速度,
+      "forceConsistentCasingInFileNames": true  // 强制文件名大小写一致,避免在不同文件系统中出现文件名大小写不一致的问题
+    }
+  }
+}