Skip to content

Commit

Permalink
Write binary gbx package and load it
Browse files Browse the repository at this point in the history
  • Loading branch information
devongovett committed Dec 3, 2017
1 parent 2e9da65 commit 9a8f6f3
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 5,959 deletions.
Binary file removed .DS_Store
Binary file not shown.
37 changes: 18 additions & 19 deletions GlimmerAsset.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
const Asset = require('parcel/src/Asset');
const {Asset} = require('parcel-bundler');
const {preprocess, traverse} = require('@glimmer/syntax');
const {precompile, TemplateCompiler} = require('@glimmer/compiler');
const {specifierFor} = require('@glimmer/bundle-compiler');
const {Project} = require('glimmer-analyzer');
const path = require('path');
const {MUCompilerDelegate} = require('@glimmer/compiler-delegates');

const project = new Project(process.cwd());

class GlimmerAsset extends Asset {
constructor(name, pkg, options) {
super(name, pkg, options);
this.type = 'gbx';

this.delegate = new MUCompilerDelegate({
projectPath: process.cwd(),
outputFiles: {
dataSegment: 'table.js',
heapFile: 'templates.gbx'
}
});
}

parse(code) {
Expand Down Expand Up @@ -83,28 +92,18 @@ class GlimmerAsset extends Asset {
}
}

generate() {
let template = TemplateCompiler.compile({meta: specifierFor(this.name, 'default')}, this.ast);

let map = path.relative(path.dirname(this.name), require.resolve('./module-map'));
this.addDependency(map);

let specifier = project.specifierForPath(path.relative(process.cwd(), this.name));
let js = `
var map = require(${JSON.stringify(map)});
module.exports = map[${JSON.stringify(specifier)}] = ${precompile(this.contents)}; // TODO: remove this when we are loading GBX file instead
`;
templateLocatorFor(absoluteModulePath) {
let normalizedPath = this.delegate.normalizePath(absoluteModulePath);
return this.delegate.templateLocatorFor({ module: normalizedPath, name: 'default' });
}

for (let dep of this.dependencies.values()) {
if (dep.specifier) {
// This is not great. Should check __esModule key or something instead.
js += `map[${JSON.stringify(dep.specifier)}] = require(${JSON.stringify(dep.name)})${dep.specifier.split(':')[0] !== 'template' ? '.default' : ''};`
}
}
generate() {
let locator = this.templateLocatorFor(this.name, 'default');
let template = TemplateCompiler.compile({meta: locator.meta}, this.ast);

return {
gbx: template.toJSON(),
js: js
js: ''
};
}
}
Expand Down
174 changes: 114 additions & 60 deletions GlimmerPackager.js
Original file line number Diff line number Diff line change
@@ -1,82 +1,136 @@
const Packager = require('parcel/src/packagers/Packager');
const {Packager} = require('parcel-bundler');
const {BundleCompiler, specifierFor} = require('@glimmer/bundle-compiler');
const {CompilableTemplate} = require('@glimmer/opcode-compiler');
const {MUCompilerDelegate, MUCodeGenerator} = require('@glimmer/compiler-delegates');
const {Project} = require('glimmer-analyzer');
const path = require('path');

const BUILTINS = ['action', '/css-blocks/components/state', '/css-blocks/components/concat'];
const CAPABILITIES = {
dynamicLayout: false,
prepareArgs: false,
elementHook: true,
staticDefinitions: false,
dynamicTag: true,
createArgs: true,
attributeHook: true
};

class GlimmerPackager extends Packager {
start() {
this.compiler = new BundleCompiler(this)
this.delegate = new MUCompilerDelegate({
projectPath: process.cwd(),
outputFiles: {
dataSegment: 'table.js',
heapFile: 'templates.gbx'
},
mainTemplateLocator: {
module: './src/ui/components/todomvc-app/template.hbs',
name: 'default'
}
});

this.codegen = new MUCodeGenerator(
this.delegate.project,
this.delegate.outputFiles,
this.delegate.builtins,
this.delegate.compilation,
this.delegate.mainTemplateLocator
);

this.compiler = new BundleCompiler(this.delegate);
this.project = new Project(process.cwd());
}

addAsset(asset) {
let specifier = specifierFor(path.relative(process.cwd(), asset.name), 'default');
this.compiler.addCustom(specifier, asset.generated.gbx);
}

hasHelperInScope(helperName, referrer) {
if (BUILTINS.indexOf(helperName) > -1) { return true; }

let referrerSpec = this.project.specifierForPath(referrer.module) || undefined;
return !!this.project.resolver.identify(`helper:${helperName}`, referrerSpec);
}

resolveHelperSpecifier(helperName, referrer) {
if (BUILTINS.indexOf(helperName) > -1) {
return specifierFor('__BUILTIN__', helperName);
}

let referrerSpec = this.project.specifierForPath(referrer.module) || undefined;
let resolvedSpec = this.project.resolver.identify(`helper:${helperName}`, referrerSpec);

if (!resolvedSpec) {
return specifierFor('__UNKNOWN__', 'default');
}
return this.getCompilerSpecifier(resolvedSpec);
}

hasComponentInScope(name, referrer) {
return !!this.project.resolver.identify(`template:${name}`, this.project.specifierForPath(referrer.module));
templateLocatorFor(absoluteModulePath) {
let normalizedPath = this.delegate.normalizePath(absoluteModulePath);
return this.delegate.templateLocatorFor({module: normalizedPath, name: 'default'});
}

resolveComponentSpecifier(name, referrer) {
let referrerSpec = this.project.specifierForPath(referrer.module);
let resolved = this.project.resolver.identify(`template:${name}`, referrerSpec);

let resolvedSpecifier = this.getCompilerSpecifier(resolved);

return resolvedSpecifier;
addAsset(asset) {
let locator = this.templateLocatorFor(asset.name);
let compilable = CompilableTemplate.topLevel(asset.generated.gbx, this.compiler.compileOptions(locator));
this.compiler.addCompilableTemplate(locator, compilable);
}

getCompilerSpecifier(specifier) {
return specifierFor(this.project.pathForSpecifier(specifier), 'default');
async end() {
let compilation = this.compiler.compile();
let table = await this.generateExternalModuleTable(compilation.table);
let meta = this.generateTemplateMetaData(compilation.table, compilation.symbolTables);

let dataSegment = {
table: table,
heap: {
table: compilation.heap.table,
handle: compilation.heap.handle
},
pool: compilation.pool,
prefix: meta.commonPrefix,
meta: meta.mergedMeta,
mainEntry: compilation.table.vmHandleByModuleLocator.get(this.delegate.mainTemplateLocator)
};

let json = JSON.stringify(dataSegment);
let buf = new Buffer(4 + json.length);
buf.writeUInt32BE(json.length, 0);
buf.write(json, 4);

await this.dest.write(buf);
await this.dest.end(new Buffer(compilation.heap.buffer));
}

getComponentCapabilities() {
return CAPABILITIES;
}
async generateExternalModuleTable(table) {
let res = [];
let project = this.project;
for (let [key, locator] of table.byHandle) {
let referrer = project.specifierForPath(path.normalize(locator.module));
if (referrer && referrer.split(':')[0] === 'template') {
let specifier = project.resolver.identify("component:", referrer);
if (!specifier) {
return null;
}

let module = './' + project.pathForSpecifier(specifier);
locator = {module, name: 'default'};
} else if (locator.kind === 'template') {
locator = null;
}

if (locator) {
let resolved = await this.bundler.resolver.resolve(locator.module, process.cwd() + '/index');
let asset = this.bundler.loadedAssets.get(resolved.path);
locator.module = asset.id;
res[key] = locator;
}
}

getComponentLayout(specifier, block, options) {
return CompilableTemplate.topLevel(block, options);
return res;
}

async end() {
let res = this.compiler.compile();
console.log(res);
console.log(this.compiler.getSpecifierMap())
await this.dest.end(new Buffer(res.heap.buffer));
generateTemplateMetaData(table, compilerSymbolTables) {
let specifiers = [];
let symbolTables = this.codegen.generateSymbolTables(compilerSymbolTables);
let map = this.codegen.generateSpecifierMap(table);

Object.keys(map).forEach((k) => {
if (specifiers.indexOf(k) === -1) {
specifiers.push(k);
}
});

Object.keys(symbolTables).forEach((k) => {
if (specifiers.indexOf(k) === -1) {
specifiers.push(k);
}
});

let commonPrefix = this.codegen.commonPrefix(specifiers);
let mergedMeta = {};

Object.keys(map).forEach(specifier => {
let trimmed = specifier.replace(commonPrefix, '');
mergedMeta[trimmed] = { h: map[specifier] };
});

Object.keys(symbolTables).forEach(specifier => {
let trimmed = specifier.replace(commonPrefix, '');
if (mergedMeta[trimmed]) {
mergedMeta[trimmed].table = symbolTables[specifier];
} else {
mergedMeta[trimmed] = { table: symbolTables[specifier] };
}
});

return {commonPrefix, mergedMeta};
}
}

Expand Down
14 changes: 9 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
"test": "ember test"
},
"devDependencies": {
"@glimmer/application": "^0.8.0-alpha.10",
"@glimmer/application": "0.9.0-alpha.12",
"@glimmer/application-pipeline": "^0.5.1",
"@glimmer/component": "^0.8.0-alpha.10",
"@glimmer/component": "0.9.0-alpha.12",
"@glimmer/resolver": "^0.4.1",
"broccoli-concat": "^3.2.2",
"broccoli-funnel": "^1.1.0",
Expand All @@ -27,16 +27,20 @@
},
"private": true,
"dependencies": {
"@glimmer/bundle-compiler": "^0.29.8",
"@glimmer/compiler": "^0.29.8",
"@glimmer/opcode-compiler": "^0.29.8",
"@glimmer/bundle-compiler": "^0.30.3",
"@glimmer/compiler": "^0.30.3",
"@glimmer/compiler-delegates": "^0.9.0-alpha.1",
"@glimmer/opcode-compiler": "^0.30.3",
"@glimmer/program": "^0.30.3",
"@glimmer/resolution-map-builder": "^0.5.1",
"@glimmer/syntax": "^0.30.3",
"@glimmer/util": "^0.29.8",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-preset-env": "^1.6.1",
"glimmer-analyzer": "^0.3.2",
"navigo": "^4.5.1",
"parcel-bundler": "^1.0.0-alpha.2",
"todomvc-app-css": "^2.1.0",
"todomvc-common": "^1.0.3"
}
Expand Down
2 changes: 1 addition & 1 deletion parcel.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const Bundler = require('parcel');
const Bundler = require('parcel-bundler');

const bundler = new Bundler('src/ui/index.html', {cache: false});
bundler.addAssetType('.hbs', require.resolve('./GlimmerAsset'));
Expand Down
64 changes: 50 additions & 14 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,56 @@
import App from './main';
import Application, { DOMBuilder, AsyncRenderer, BytecodeLoader } from '@glimmer/application';
import { ComponentManager, setPropertyDidChange } from '@glimmer/component';
import Resolver, { ResolverConfiguration, BasicModuleRegistry } from '@glimmer/resolver';
import './ui/components/todomvc-app/template.hbs';

const app = new App();
console.log(app)
const containerElement = document.getElementById('app');
// TODO: generate this somehow?
var resolverConfiguration = { "app": { "name": "glimmer-todomvc", "rootName": "glimmer-todomvc" }, "types": { "application": { "definitiveCollection": "main" }, "component": { "definitiveCollection": "components" }, "helper": { "definitiveCollection": "components" }, "renderer": { "definitiveCollection": "main" }, "template": { "definitiveCollection": "components" } }, "collections": { "main": { "types": ["application", "renderer"] }, "components": { "group": "ui", "types": ["component", "template", "helper"], "defaultType": "component", "privateCollections": ["utils"] }, "styles": { "group": "ui", "unresolvable": true }, "utils": { "unresolvable": true } } };

setPropertyDidChange(() => {
app.scheduleRerender();
});
fetch('/dist/glimmer-todomvc.gbx')
.then(req => req.arrayBuffer())
.then(buf => {
// separate data segment from bytecode
let view = new DataView(buf);
let dec = new TextDecoder;
let length = view.getUint32(0);
let json = dec.decode(new Uint8Array(buf, 4, length));
let data = JSON.parse(json);
let bytecode = buf.slice(4 + length);

app.registerInitializer({
initialize(registry) {
registry.register(`component-manager:/${app.rootName}/component-managers/main`, ComponentManager)
}
});
// resolve modules
data.table = data.table.map(locator => {
if (locator.kind === 'helper') {
let type = locator.meta.factory ? 0 : 1;
return [type, loadModule(locator)];
} else {
return loadModule(locator);
}
});

app.boot();
function loadModule(locator) {
let res = require(locator.module);
if (locator.name === 'default') {
return res.default || res;
}

app.renderComponent('todomvc-app', containerElement, null);
return res[locator.name];
}

let element = document.getElementById('app');

let app = new Application({
builder: new DOMBuilder({ element }),
renderer: new AsyncRenderer(),
loader: new BytecodeLoader({ data, bytecode }),
resolver: new Resolver(resolverConfiguration, new BasicModuleRegistry(data.meta)),
});

app.registerInitializer({
initialize(registry) {
registry.register(`component-manager:/${app.rootName}/component-managers/main`, ComponentManager);
}
});

app.boot();
app.renderComponent('todomvc-app', element, null);
});
Loading

0 comments on commit 9a8f6f3

Please sign in to comment.