This commit is contained in:
熊潇 2025-04-01 13:51:33 +08:00
parent ad346ee25b
commit 3ac31e4d09
8 changed files with 254 additions and 15 deletions

View File

@ -1 +1,8 @@
# vite-react-template webcontainers 备注
核心还是要联网否则无法使用因为使用的请求stackblitz.com 的api

View File

@ -1,13 +1,27 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head>
<head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title> <title>Vite + React + TS</title>
</head> <style>
<body> iframe {
width: 784px;
min-height: 100px;
}
#root {
width: 784px;
min-height: 100px;
}
</style>
</head>
<body>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/main.tsx"></script> <script type="module" src="/src/files/main.ts"></script>
</body> </body>
</html> </html>

View File

@ -17,6 +17,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@kevisual/router": "0.0.9", "@kevisual/router": "0.0.9",
"@webcontainer/api": "^1.5.3",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
@ -34,6 +35,7 @@
"@types/node": "^22.13.13", "@types/node": "^22.13.13",
"@types/react": "^19.0.12", "@types/react": "^19.0.12",
"@types/react-dom": "^19.0.4", "@types/react-dom": "^19.0.4",
"@vitejs/plugin-basic-ssl": "^2.0.0",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"tailwindcss": "^4.0.16", "tailwindcss": "^4.0.16",
"typescript": "^5.8.2", "typescript": "^5.8.2",

21
pnpm-lock.yaml generated
View File

@ -11,6 +11,9 @@ importers:
'@kevisual/router': '@kevisual/router':
specifier: 0.0.9 specifier: 0.0.9
version: 0.0.9 version: 0.0.9
'@webcontainer/api':
specifier: ^1.5.3
version: 1.5.3
clsx: clsx:
specifier: ^2.1.1 specifier: ^2.1.1
version: 2.1.1 version: 2.1.1
@ -57,6 +60,9 @@ importers:
'@types/react-dom': '@types/react-dom':
specifier: ^19.0.4 specifier: ^19.0.4
version: 19.0.4(@types/react@19.0.12) version: 19.0.4(@types/react@19.0.12)
'@vitejs/plugin-basic-ssl':
specifier: ^2.0.0
version: 2.0.0(vite@6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.5.1))
'@vitejs/plugin-react': '@vitejs/plugin-react':
specifier: ^4.3.4 specifier: ^4.3.4
version: 4.3.4(vite@6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.5.1)) version: 4.3.4(vite@6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.5.1))
@ -557,12 +563,21 @@ packages:
'@types/react@19.0.12': '@types/react@19.0.12':
resolution: {integrity: sha512-V6Ar115dBDrjbtXSrS+/Oruobc+qVbbUxDFC1RSbRqLt5SYvxxyIDrSC85RWml54g+jfNeEMZhEj7wW07ONQhA==} resolution: {integrity: sha512-V6Ar115dBDrjbtXSrS+/Oruobc+qVbbUxDFC1RSbRqLt5SYvxxyIDrSC85RWml54g+jfNeEMZhEj7wW07ONQhA==}
'@vitejs/plugin-basic-ssl@2.0.0':
resolution: {integrity: sha512-gc9Tjg8bUxBVSTzeWT3Njc0Cl3PakHFKdNfABnZWiUgbxqmHDEn7uECv3fHVylxoYgNzAcmU7ZrILz+BwSo3sA==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
peerDependencies:
vite: ^6.0.0
'@vitejs/plugin-react@4.3.4': '@vitejs/plugin-react@4.3.4':
resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies: peerDependencies:
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 vite: ^4.2.0 || ^5.0.0 || ^6.0.0
'@webcontainer/api@1.5.3':
resolution: {integrity: sha512-f6Oq3ohtSC5RYABhpN8aVOVHpcKvJ1fB1jjuvODTBU5u6BcroYEhphnrywdw8RO+2Vy5ekCdKe5D4dCMdMSrzA==}
abort-controller@3.0.0: abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'} engines: {node: '>=6.5'}
@ -1454,6 +1469,10 @@ snapshots:
dependencies: dependencies:
csstype: 3.1.3 csstype: 3.1.3
'@vitejs/plugin-basic-ssl@2.0.0(vite@6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.5.1))':
dependencies:
vite: 6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.5.1)
'@vitejs/plugin-react@4.3.4(vite@6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.5.1))': '@vitejs/plugin-react@4.3.4(vite@6.2.3(@types/node@22.13.13)(jiti@2.4.2)(lightningcss@1.29.2)(yaml@2.5.1))':
dependencies: dependencies:
'@babel/core': 7.26.0 '@babel/core': 7.26.0
@ -1465,6 +1484,8 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@webcontainer/api@1.5.3': {}
abort-controller@3.0.0: abort-controller@3.0.0:
dependencies: dependencies:
event-target-shim: 5.0.1 event-target-shim: 5.0.1

63
src/files/demo.ts Normal file
View File

@ -0,0 +1,63 @@
/** @type {import('@webcontainer/api').FileSystemTree} */
import { WebContainer } from '@webcontainer/api';
// Call only once
const webcontainerInstance = await WebContainer.boot();
export const files = {
'package.json': {
file: {
contents: `
{
"name": "vite-starter",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"vite": "^4.0.4"
}
}`,
},
},
'index.html': {
file: {
contents: `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>`,
},
},
};
await webcontainerInstance.mount(files);
async function startDevServer() {
const installProcess = await webcontainerInstance.spawn('npm', ['install']);
const installExitCode = await installProcess.exit;
if (installExitCode !== 0) {
throw new Error('Unable to run npm install');
}
// `npm run dev`
await webcontainerInstance.spawn('npm', ['run', 'dev']);
// webcontainerInstance.on('server-ready', (port, url) => (iframeEl.src = url));
}
startDevServer();

54
src/files/files.ts Normal file
View File

@ -0,0 +1,54 @@
export const files = {
'package.json': {
file: {
contents: `
{
"name": "vite-starter",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"start": "vite",
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"vite": "^4.0.4",
"tailwindcss": "^3.3.5"
}
}`,
},
},
'a.js': {
file: {
contents: `
console.log('Hello, world!');
`,
},
},
'style.css': {
file: {
contents: `
@import 'tailwindcss';
`,
},
},
'index.html': {
file: {
contents: `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app">app的内容</div>
<script type="module" src="a.js"></script>
</body>
</html>`,
},
},
};

74
src/files/main.ts Normal file
View File

@ -0,0 +1,74 @@
import { WebContainer } from '@webcontainer/api';
import { files } from './files';
/** @type {import('@webcontainer/api').WebContainer} */
let webcontainerInstance;
/** @type {HTMLIFrameElement | null} */
const iframeEl = document.querySelector('iframe');
/** @type {HTMLTextAreaElement | null} */
const textareaEl = document.querySelector('textarea');
window.addEventListener('load', async () => {
// textareaEl.value = files['index.js'].file.contents;
// textareaEl.addEventListener('input', (e) => {
// writeIndexJS(e.currentTarget.value);
// });
const hasLoaded = document.querySelector('.container') !== null;
if (hasLoaded) {
return;
}
console.log('@hasLoaded', document.querySelector('.container'), hasLoaded);
document.querySelector('#root')!.innerHTML = `
<div class="container">
<div class="editor">
<textarea>I am a textarea</textarea>
</div>
<div class="preview">
<iframe src=""></iframe>
</div>
</div>
`;
console.log('@root', document.querySelector('#root'));
console.log('@window.addEventListener', document.querySelector('#root'));
// Call only once
webcontainerInstance = await WebContainer.boot();
await webcontainerInstance.mount(files);
const exitCode = await installDependencies();
if (exitCode !== 0) {
throw new Error('Installation failed');
}
console.log('Installation successful');
startDevServer();
});
async function installDependencies() {
// Install dependencies
const installProcess = await webcontainerInstance.spawn('npm', ['install']);
installProcess.output.pipeTo(
new WritableStream({
write(data) {
console.log(data);
},
}),
);
// Wait for install command to exit
return installProcess.exit;
}
async function startDevServer() {
// Run `npm run start` to start the Express app
await webcontainerInstance.spawn('npm', ['run', 'start']);
// Wait for `server-ready` event
webcontainerInstance.on('server-ready', (port, url) => {
console.log('@server-ready', port, url);
const iframe = document.querySelector('iframe');
if (iframe) {
iframe.src = url;
}
});
}

View File

@ -3,7 +3,7 @@ import react from '@vitejs/plugin-react';
import path from 'path'; import path from 'path';
import tailwindcss from '@tailwindcss/vite'; import tailwindcss from '@tailwindcss/vite';
import pkgs from './package.json' with { type: 'json' }; import pkgs from './package.json' with { type: 'json' };
import basicSsl from '@vitejs/plugin-basic-ssl';
const version = pkgs.version || '0.0.1'; const version = pkgs.version || '0.0.1';
const isDev = process.env.NODE_ENV === 'development'; const isDev = process.env.NODE_ENV === 'development';
@ -12,7 +12,7 @@ const basename = isDev ? '/' : pkgs?.basename || '/';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react(), tailwindcss()], plugins: [react(), tailwindcss(), basicSsl() ],
resolve: { resolve: {
alias: { alias: {
'@': path.resolve(__dirname, './src'), '@': path.resolve(__dirname, './src'),
@ -28,8 +28,12 @@ export default defineConfig({
target: 'esnext', target: 'esnext',
}, },
server: { server: {
port: 6004, port: 7004,
host: '0.0.0.0', host: '0.0.0.0',
headers: {
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Opener-Policy': 'same-origin',
},
proxy: { proxy: {
'/api': { '/api': {
target: 'http://localhost:3000', target: 'http://localhost:3000',