Skip to content

Commit 1707879

Browse files
committed
feat(vue-playground): init package
1 parent 17ae71d commit 1707879

40 files changed

+2689
-3
lines changed

packages/superman-shared/src/build-utils/build-script.util.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,12 @@ export async function changeViteConfig(config: UserConfig, options: ChangeConfig
4343
}
4444
}
4545

46+
export type ChangeConfigFn = (config: UserConfig, options: ChangeConfigOptions) => Promise<InlineConfig>
4647
export interface BuildOptions {
4748
minifyConfig: UserConfig
4849
unMinifyConfig: UserConfig
4950
packagePath: string
50-
changeConfigFn?: (config: UserConfig, options: ChangeConfigOptions) => Promise<InlineConfig>
51+
changeConfigFn?: ChangeConfigFn
5152
}
5253

5354
export async function build(config: BuildOptions) {
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
{
2+
"name": "vue-playground",
3+
"version": "0.1.4",
4+
"description": "a codesandbox playground for vue",
5+
"keywords": [
6+
"vue",
7+
"vue-playground",
8+
"codesandbox",
9+
"playground",
10+
"sandbox",
11+
"vue-superman",
12+
"component",
13+
"utils"
14+
],
15+
"author": "yangjinming <https://github.com/2214962083>",
16+
"funding": "https://github.com/sponsors/2214962083",
17+
"license": "MIT",
18+
"private": false,
19+
"scripts": {
20+
"build": "vue-tsc --noEmit &&esno ./scripts/build.ts",
21+
"build:watch": "cross-env WATCH=true pnpm build",
22+
"clean": "rimraf ./dist/**/*",
23+
"dev": "vite",
24+
"test": "vitest run --silent --passWithNoTests",
25+
"test:watch": "pnpm test -- --watch",
26+
"type-check": "tsc --noEmit"
27+
},
28+
"main": "./dist/index.cjs",
29+
"module": "./dist/index.mjs",
30+
"types": "./dist/index.d.ts",
31+
"unpkg": "./dist/index.min.umd.js",
32+
"jsdelivr": "./dist/index.min.umd.js",
33+
"exports": {
34+
".": {
35+
"require": "./dist/index.cjs",
36+
"import": "./dist/index.mjs",
37+
"types": "./dist/index.d.ts"
38+
},
39+
"./*": "./*"
40+
},
41+
"files": [
42+
"dist"
43+
],
44+
"sideEffects": false,
45+
"repository": {
46+
"type": "git",
47+
"url": "git+https://github.com/2214962083/vue-superman.git",
48+
"directory": "packages/vue-playground"
49+
},
50+
"bugs": {
51+
"url": "https://github.com/2214962083/vue-superman/issues"
52+
},
53+
"homepage": "https://github.com/2214962083/vue-superman#readme",
54+
"peerDependencies": {
55+
"vue": "^3.2.0"
56+
},
57+
"dependencies": {
58+
"@babel/types": "^7.17.10",
59+
"@vue/reactivity": "^3.2.33",
60+
"@vue/runtime-core": "^3.2.33",
61+
"@vue/runtime-dom": "^3.2.33",
62+
"@vue/shared": "^3.2.33",
63+
"@vueuse/core": "^8.4.1",
64+
"emmet-monaco-es": "^5.1.0",
65+
"js-base64": "^3.7.2",
66+
"monaco-editor": "^0.33.0",
67+
"sucrase": "^3.21.0",
68+
"theme-vitesse": "^0.4.9"
69+
},
70+
"devDependencies": {
71+
"@types/node": "^17.0.24",
72+
"@types/rimraf": "^3.0.2",
73+
"@vitejs/plugin-vue": "^2.3.1",
74+
"@vitejs/plugin-vue-jsx": "^1.3.10",
75+
"@vue/test-utils": "^2.0.0-rc.21",
76+
"conventional-changelog-cli": "^2.2.2",
77+
"cross-env": "^7.0.3",
78+
"esno": "^0.14.1",
79+
"jsdom": "^19.0.0",
80+
"rimraf": "^3.0.2",
81+
"superman-shared": "workspace:*",
82+
"typescript": "4.6.3",
83+
"vite": "^2.9.5",
84+
"vitest": "^0.9.3",
85+
"vue": "^3.2.31",
86+
"vue-tsc": "^0.34.7"
87+
}
88+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {buildUtils} from 'superman-shared'
2+
import {minifyConfig, unMinifyConfig, packagePath} from '../vite.config'
3+
4+
const changeConfigFn: buildUtils.ChangeConfigFn = (config, options) => {
5+
if (!config.build) config.build = {}
6+
if (!config.build.rollupOptions) config.build.rollupOptions = {}
7+
if (!config.build.rollupOptions.output) config.build.rollupOptions.output = {}
8+
config.build.rollupOptions.output = {
9+
inlineDynamicImports: false,
10+
manualChunks: {
11+
monaco: ['monaco-editor', 'emmet-monaco-es']
12+
}
13+
}
14+
return buildUtils.changeViteConfig(config, options)
15+
}
16+
17+
buildUtils.build({
18+
minifyConfig,
19+
unMinifyConfig,
20+
packagePath,
21+
changeConfigFn
22+
})
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from './module-compiler'
2+
export * from './preview-proxy'
3+
export * from './preview'
4+
export * from './transform'
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import {File, Store} from '../store'
3+
import {
4+
babelParse,
5+
MagicString,
6+
walk,
7+
walkIdentifiers,
8+
extractIdentifiers,
9+
isInDestructureAssignment,
10+
isStaticProperty
11+
} from 'vue/compiler-sfc'
12+
import {ExportSpecifier, Identifier, Node} from '@babel/types'
13+
14+
export function compileModulesForPreview(store: Store) {
15+
const seen = new Set<File>()
16+
const processed: string[] = []
17+
processFile(store, store.state.files[store.state.mainFile], processed, seen)
18+
19+
// also add css files that are not imported
20+
for (const filename in store.state.files) {
21+
if (filename.endsWith('.css')) {
22+
const file = store.state.files[filename]
23+
if (!seen.has(file)) {
24+
processed.push(`\nwindow.__css__ += ${JSON.stringify(file.compiled.css)}`)
25+
}
26+
}
27+
}
28+
29+
return processed
30+
}
31+
32+
const modulesKey = `__modules__`
33+
const exportKey = `__export__`
34+
const dynamicImportKey = `__dynamic_import__`
35+
const moduleKey = `__module__`
36+
37+
// similar logic with Vite's SSR transform, except this is targeting the browser
38+
function processFile(store: Store, file: File, processed: string[], seen: Set<File>) {
39+
if (seen.has(file)) {
40+
return []
41+
}
42+
seen.add(file)
43+
44+
if (file.filename.endsWith('.html')) {
45+
return processHtmlFile(store, file.code, file.filename, processed, seen)
46+
}
47+
48+
// eslint-disable-next-line prefer-const
49+
let [js, importedFiles] = processModule(store, file.compiled.js, file.filename)
50+
// append css
51+
if (file.compiled.css) {
52+
js += `\nwindow.__css__ += ${JSON.stringify(file.compiled.css)}`
53+
}
54+
// crawl child imports
55+
if (importedFiles.size) {
56+
for (const imported of importedFiles) {
57+
processFile(store, store.state.files[imported], processed, seen)
58+
}
59+
}
60+
// push self
61+
processed.push(js)
62+
}
63+
64+
function processModule(store: Store, src: string, filename: string): [string, Set<string>] {
65+
const s = new MagicString(src)
66+
67+
const ast = babelParse(src, {
68+
sourceFilename: filename,
69+
sourceType: 'module'
70+
}).program.body
71+
72+
const idToImportMap = new Map<string, string>()
73+
const declaredConst = new Set<string>()
74+
const importedFiles = new Set<string>()
75+
const importToIdMap = new Map<string, string>()
76+
77+
function defineImport(node: Node, source: string) {
78+
const filename = source.replace(/^\.\/+/, '')
79+
if (!(filename in store.state.files)) {
80+
throw new Error(`File "${filename}" does not exist.`)
81+
}
82+
if (importedFiles.has(filename)) {
83+
return importToIdMap.get(filename)!
84+
}
85+
importedFiles.add(filename)
86+
const id = `__import_${importedFiles.size}__`
87+
importToIdMap.set(filename, id)
88+
s.appendLeft(node.start!, `const ${id} = ${modulesKey}[${JSON.stringify(filename)}]\n`)
89+
return id
90+
}
91+
92+
function defineExport(name: string, local = name) {
93+
s.append(`\n${exportKey}(${moduleKey}, "${name}", () => ${local})`)
94+
}
95+
96+
// 0. instantiate module
97+
s.prepend(`const ${moduleKey} = __modules__[${JSON.stringify(filename)}] = { [Symbol.toStringTag]: "Module" }\n\n`)
98+
99+
// 1. check all import statements and record id -> importName map
100+
for (const node of ast) {
101+
// import foo from 'foo' --> foo -> __import_foo__.default
102+
// import { baz } from 'foo' --> baz -> __import_foo__.baz
103+
// import * as ok from 'foo' --> ok -> __import_foo__
104+
if (node.type === 'ImportDeclaration') {
105+
const source = node.source.value
106+
if (source.startsWith('./')) {
107+
const importId = defineImport(node, node.source.value)
108+
for (const spec of node.specifiers) {
109+
if (spec.type === 'ImportSpecifier') {
110+
idToImportMap.set(spec.local.name, `${importId}.${(spec.imported as Identifier).name}`)
111+
} else if (spec.type === 'ImportDefaultSpecifier') {
112+
idToImportMap.set(spec.local.name, `${importId}.default`)
113+
} else {
114+
// namespace specifier
115+
idToImportMap.set(spec.local.name, importId)
116+
}
117+
}
118+
s.remove(node.start!, node.end!)
119+
}
120+
}
121+
}
122+
123+
// 2. check all export statements and define exports
124+
for (const node of ast) {
125+
// named exports
126+
if (node.type === 'ExportNamedDeclaration') {
127+
if (node.declaration) {
128+
if (node.declaration.type === 'FunctionDeclaration' || node.declaration.type === 'ClassDeclaration') {
129+
// export function foo() {}
130+
defineExport(node.declaration.id!.name)
131+
} else if (node.declaration.type === 'VariableDeclaration') {
132+
// export const foo = 1, bar = 2
133+
for (const decl of node.declaration.declarations) {
134+
for (const id of extractIdentifiers(decl.id)) {
135+
defineExport(id.name)
136+
}
137+
}
138+
}
139+
s.remove(node.start!, node.declaration.start!)
140+
} else if (node.source) {
141+
// export { foo, bar } from './foo'
142+
const importId = defineImport(node, node.source.value)
143+
for (const spec of node.specifiers) {
144+
defineExport((spec.exported as Identifier).name, `${importId}.${(spec as ExportSpecifier).local.name}`)
145+
}
146+
s.remove(node.start!, node.end!)
147+
} else {
148+
// export { foo, bar }
149+
for (const spec of node.specifiers) {
150+
const local = (spec as ExportSpecifier).local.name
151+
const binding = idToImportMap.get(local)
152+
defineExport((spec.exported as Identifier).name, binding || local)
153+
}
154+
s.remove(node.start!, node.end!)
155+
}
156+
}
157+
158+
// default export
159+
if (node.type === 'ExportDefaultDeclaration') {
160+
if ('id' in node.declaration && node.declaration.id) {
161+
// named hoistable/class exports
162+
// export default function foo() {}
163+
// export default class A {}
164+
const {name} = node.declaration.id
165+
s.remove(node.start!, node.start! + 15)
166+
s.append(`\n${exportKey}(${moduleKey}, "default", () => ${name})`)
167+
} else {
168+
// anonymous default exports
169+
s.overwrite(node.start!, node.start! + 14, `${moduleKey}.default =`)
170+
}
171+
}
172+
173+
// export * from './foo'
174+
if (node.type === 'ExportAllDeclaration') {
175+
const importId = defineImport(node, node.source.value)
176+
s.remove(node.start!, node.end!)
177+
s.append(`\nfor (const key in ${importId}) {
178+
if (key !== 'default') {
179+
${exportKey}(${moduleKey}, key, () => ${importId}[key])
180+
}
181+
}`)
182+
}
183+
}
184+
185+
// 3. convert references to import bindings
186+
for (const node of ast) {
187+
if (node.type === 'ImportDeclaration') continue
188+
walkIdentifiers(node, (id, parent, parentStack) => {
189+
const binding = idToImportMap.get(id.name)
190+
if (!binding) {
191+
return
192+
}
193+
if (isStaticProperty(parent) && parent.shorthand) {
194+
// let binding used in a property shorthand
195+
// { foo } -> { foo: __import_x__.foo }
196+
// skip for destructure patterns
197+
if (!(parent as any).inPattern || isInDestructureAssignment(parent, parentStack)) {
198+
s.appendLeft(id.end!, `: ${binding}`)
199+
}
200+
} else if (parent.type === 'ClassDeclaration' && id === parent.superClass) {
201+
if (!declaredConst.has(id.name)) {
202+
declaredConst.add(id.name)
203+
// locate the top-most node containing the class declaration
204+
const topNode = parentStack[1]
205+
s.prependRight(topNode.start!, `const ${id.name} = ${binding};\n`)
206+
}
207+
} else {
208+
s.overwrite(id.start!, id.end!, binding)
209+
}
210+
})
211+
}
212+
213+
// 4. convert dynamic imports
214+
;(walk as any)(ast, {
215+
enter(node: Node, parent: Node) {
216+
if (node.type === 'Import' && parent.type === 'CallExpression') {
217+
const arg = parent.arguments[0]
218+
if (arg.type === 'StringLiteral' && arg.value.startsWith('./')) {
219+
s.overwrite(node.start!, node.start! + 6, dynamicImportKey)
220+
s.overwrite(arg.start!, arg.end!, JSON.stringify(arg.value.replace(/^\.\/+/, '')))
221+
}
222+
}
223+
}
224+
})
225+
226+
return [s.toString(), importedFiles]
227+
}
228+
229+
const scriptRE = /<script\b(?:\s[^>]*>|>)([^]*?)<\/script>/gi
230+
const scriptModuleRE = /<script\b[^>]*type\s*=\s*(?:"module"|'module')[^>]*>([^]*?)<\/script>/gi
231+
232+
function processHtmlFile(store: Store, src: string, filename: string, processed: string[], seen: Set<File>) {
233+
const deps: string[] = []
234+
let jsCode = ''
235+
const html = src
236+
.replace(scriptModuleRE, (_, content) => {
237+
const [code, importedFiles] = processModule(store, content, filename)
238+
if (importedFiles.size) {
239+
for (const imported of importedFiles) {
240+
processFile(store, store.state.files[imported], deps, seen)
241+
}
242+
}
243+
jsCode += '\n' + code
244+
return ''
245+
})
246+
.replace(scriptRE, (_, content) => {
247+
jsCode += '\n' + content
248+
return ''
249+
})
250+
processed.push(`document.body.innerHTML = ${JSON.stringify(html)}`)
251+
processed.push(...deps)
252+
processed.push(jsCode)
253+
}

0 commit comments

Comments
 (0)