Skip to content

Commit

Permalink
feat: use file system isolate sfc file
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaoluoboding committed Apr 22, 2021
1 parent 9ca1f08 commit 6223a4e
Show file tree
Hide file tree
Showing 12 changed files with 359 additions and 216 deletions.
30 changes: 0 additions & 30 deletions lib/index.d.ts

This file was deleted.

1 change: 0 additions & 1 deletion lib/index.umd.min.js

This file was deleted.

128 changes: 82 additions & 46 deletions lib/index.cjs.js → lib/sfc2esm.cjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@

Object.defineProperty(exports, '__esModule', { value: true });

var defaultCompiler = require('@vue/compiler-sfc/dist/compiler-sfc.esm-browser');
var compilerSfc = require('@vue/compiler-sfc');
var shared = require('@vue/shared');
var vue = require('vue');
var defaultCompiler = require('@vue/compiler-sfc/dist/compiler-sfc.esm-browser');
var Crypto = require('crypto');

function _interopNamespace(e) {
Expand All @@ -35,7 +36,11 @@ function _interopNamespace(e) {
var defaultCompiler__namespace = /*#__PURE__*/_interopNamespace(defaultCompiler);
var Crypto__namespace = /*#__PURE__*/_interopNamespace(Crypto);

const MAIN_FILE = 'App.vue';
const hashId = (filename) => {
const hashDigest = Crypto__namespace.createHash('sha256').update(filename).digest('base64'); // hash the message
return hashDigest.slice(0, 16);
};

const COMP_IDENTIFIER = `__sfc__`;
/**
* The default SFC compiler we are using is built from each commit
Expand Down Expand Up @@ -204,21 +209,38 @@ function doCompileTemplate(descriptor, id, bindingMetadata, ssr) {
}
const fnName = ssr ? `ssrRender` : `render`;
return (`\n${templateResult.code.replace(/\nexport (function|const) (render|ssrRender)/, `$1 ${fnName}`)}` + `\n${COMP_IDENTIFIER}.${fnName} = ${fnName}`);
}
async function hashId(filename) {
const hashDigest = Crypto__namespace.createHash('sha256').update(filename).digest('base64'); // hash the message
return hashDigest.slice(0, 16);
}

const welcomeCode = `
<template>
const APP_FILE = `App.vue`;
const MAIN_FILE = `main.js`;
const getMainCode = appFile => {
return `import { createApp as _createApp } from "vue"
if (window.__app__) {
window.__app__.unmount()
document.getElementById('app').innerHTML = ''
}
document.getElementById('__sfc-styles').innerHTML = window.__css__
const app = window.__app__ = _createApp(__modules__["${appFile}"].default)
app.config.errorHandler = e => console.error(e)
app.mount('#app')
`.trim();
};
const WELCOME_CODE = `<template>
<h1>{{ msg }}</h1>
</template>
<script setup>
const msg = 'Hello World!'
</script>
`.trim();
const MAIN_CODE = getMainCode(APP_FILE);
const IMPORT_MAP_CODE = `
{
"imports": {
}
}`.trim();
class File {
constructor(filename, code = '') {
this.compiled = {
Expand All @@ -233,12 +255,13 @@ class File {
let files = {};
{
files = {
'App.vue': new File(MAIN_FILE, welcomeCode)
[APP_FILE]: new File(APP_FILE, WELCOME_CODE),
[MAIN_FILE]: new File(MAIN_FILE, MAIN_CODE)
};
}
const store = vue.reactive({
files,
activeFilename: MAIN_FILE,
activeFilename: APP_FILE,
get activeFile() {
return store.files[store.activeFilename];
},
Expand All @@ -248,48 +271,68 @@ const store = vue.reactive({
},
errors: []
});
console.log(store.files[MAIN_FILE]);
console.log(store.files);
vue.watchEffect(() => compileFile(store.activeFile));
const activeFilename = vue.computed(() => store.activeFilename);
const mainCode = vue.computed(() => getMainCode(store.activeFilename));
for (const file in store.files) {
if (file !== MAIN_FILE) {
if (file !== APP_FILE) {
compileFile(store.files[file]);
}
}
// watchEffect(() => {
// history.replaceState({}, '', '#' + btoa(JSON.stringify(exportFiles())))
// })
const encodeFiles = () => btoa(JSON.stringify(exportFiles()));
function exportFiles() {
const exported = {};
for (const filename in store.files) {
exported[filename] = store.files[filename].code;
}
return exported;
}
function setActive(filename) {
function setActive(filename, code) {
store.activeFilename = filename;
store.activeFile.code = code;
}
function addFile(filename) {
function addFile(filename, code) {
if (!filename.endsWith('.vue') &&
!filename.endsWith('.js') &&
filename !== 'import-map.json') {
store.errors = [`Sandbox only supports *.vue, *.js files or import-map.json.`];
return;
}
if (filename in store.files) {
store.errors = [`File "${filename}" already exists.`];
return;
}
const file = (store.files[filename] = new File(filename));
if (filename === 'import-map.json') {
file.code = `
{
"imports": {
}
}`.trim();
file.code = IMPORT_MAP_CODE;
}
else {
file.code = code;
}
setActive(filename, file.code);
}
function changeFile(filename, code) {
if (!(filename in store.files)) {
store.errors = [`File "${filename}" is not exists.`];
return;
}
setActive(filename);
const file = store.files[filename];
setActive(file.filename, code);
}
function deleteFile(filename) {
if (confirm(`Are you sure you want to delete ${filename}?`)) {
if (store.activeFilename === filename) {
store.activeFilename = MAIN_FILE;
store.activeFilename = APP_FILE;
}
delete store.files[filename];
}
}

async function compileModules() {
const modules = await processFile(store.files[MAIN_FILE]);
async function compileModules(filename) {
if (filename !== activeFilename.value)
return [];
const modules = await processFile(store.files[filename]);
const styles = [
'color: white',
'background: #42b983',
Expand All @@ -312,8 +355,8 @@ async function processFile(file, seen = new Set()) {
seen.add(file);
await compileFile(file);
const { js, css } = file.compiled;
const s = new defaultCompiler.MagicString(js);
const ast = defaultCompiler.babelParse(js, {
const s = new compilerSfc.MagicString(js);
const ast = compilerSfc.babelParse(js, {
sourceFilename: file.filename,
sourceType: 'module',
plugins: [...shared.babelParserDefaultPlugins]
Expand All @@ -339,22 +382,8 @@ async function processFile(file, seen = new Set()) {
function defineExport(name, local = name) {
s.append(`\n${exportKey}(${moduleKey}, "${name}", () => ${local})`);
}
function defineAmount() {
return `
import { createApp as _createApp } from "vue"
if (window.__app__) {
window.__app__.unmount()
document.getElementById('app').innerHTML = ''
}
document.getElementById('__sfc-styles').innerHTML = window.__css__
const app = window.__app__ = _createApp(__modules__["${MAIN_FILE}"].default)
app.config.errorHandler = e => console.error(e)
app.mount('#app')`.trim();
}
// 0. instantiate module
s.prepend(`window.__modules__ = {};\nwindow.__css__ = ''\n\nconst ${moduleKey} = __modules__[${JSON.stringify(file.filename)}] = { [Symbol.toStringTag]: "Module" }\n\n`);
s.prepend(`window.__modules__ = {}\nwindow.__css__ = ''\n\nconst ${moduleKey} = __modules__[${JSON.stringify(file.filename)}] = { [Symbol.toStringTag]: "Module" }\n\n`);
// 1. check all import statements and record id -> importName map
for (const node of ast) {
// import foo from 'foo' --> foo -> __import_foo__.default
Expand Down Expand Up @@ -438,7 +467,7 @@ app.mount('#app')`.trim();
for (const node of ast) {
if (node.type === 'ImportDeclaration')
continue;
defaultCompiler.walkIdentifiers(node, (id, parent, parentStack) => {
compilerSfc.walkIdentifiers(node, (id, parent, parentStack) => {
const binding = idToImportMap.get(id.name);
if (!binding) {
return;
Expand Down Expand Up @@ -466,7 +495,7 @@ app.mount('#app')`.trim();
}
});
}
defaultCompiler.walk(ast, {
compilerSfc.walk(ast, {
enter(node, parent) {
if (node.type === 'Import' && parent.type === 'CallExpression') {
const arg = parent.arguments[0];
Expand All @@ -481,7 +510,7 @@ app.mount('#app')`.trim();
if (css) {
s.append(`\nwindow.__css__ += ${JSON.stringify(css)}`);
}
const processed = [defineAmount(), s.toString()];
const processed = [mainCode.value, s.toString()];
if (importedFiles.size) {
for (const imported of importedFiles) {
const fileList = await processFile(store.files[imported], seen);
Expand Down Expand Up @@ -549,13 +578,20 @@ function isInDestructureAssignment(parent, parentStack) {
return false;
}

exports.APP_FILE = APP_FILE;
exports.COMP_IDENTIFIER = COMP_IDENTIFIER;
exports.File = File;
exports.MAIN_CODE = MAIN_CODE;
exports.MAIN_FILE = MAIN_FILE;
exports.WELCOME_CODE = WELCOME_CODE;
exports.activeFilename = activeFilename;
exports.addFile = addFile;
exports.changeFile = changeFile;
exports.compileFile = compileFile;
exports.compileModules = compileModules;
exports.deleteFile = deleteFile;
exports.encodeFiles = encodeFiles;
exports.exportFiles = exportFiles;
exports.mainCode = mainCode;
exports.setActive = setActive;
exports.store = store;
39 changes: 39 additions & 0 deletions lib/sfc2esm.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as vue from 'vue';

declare function compileModules(filename: string): Promise<Array<string>>;

declare const APP_FILE = "App.vue";
declare const MAIN_FILE = "main.js";
declare const WELCOME_CODE: string;
declare const MAIN_CODE: string;
declare class File {
filename: string;
code: string;
compiled: {
js: string;
css: string;
ssr: string;
};
constructor(filename: string, code?: string);
}
interface Store {
files: Record<string, File>;
activeFilename: string;
readonly activeFile: File;
readonly importMap: string | undefined;
errors: (string | Error)[];
}
declare const store: Store;
declare const activeFilename: vue.ComputedRef<string>;
declare const mainCode: vue.ComputedRef<string>;
declare const encodeFiles: () => string;
declare function exportFiles(): Record<string, string>;
declare function setActive(filename: string, code: string): void;
declare function addFile(filename: string, code: string): void;
declare function changeFile(filename: string, code: string): void;
declare function deleteFile(filename: string): void;

declare const COMP_IDENTIFIER = "__sfc__";
declare function compileFile({ filename, code, compiled }: File): Promise<void>;

export { APP_FILE, COMP_IDENTIFIER, File, MAIN_CODE, MAIN_FILE, Store, WELCOME_CODE, activeFilename, addFile, changeFile, compileFile, compileModules, deleteFile, encodeFiles, exportFiles, mainCode, setActive, store };
Loading

0 comments on commit 6223a4e

Please sign in to comment.