Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@embroider/core

Package Overview
Dependencies
Maintainers
1
Versions
486
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@embroider/core - npm Package Compare versions

Comparing version 0.0.2 to 0.0.3

src/app-differ.d.ts

28

notes.md
# Stack
- externals detection should take into account which deps are ember-addons and whether or not we have ember-auto-import
- would be nice to use package-cache for this, which implies refactoring so its available inside v1-addon
- this is a good time to split the phase1 and phase2 layers into separate packages
- prune away travis-web changes to make it easier to test current (where to put sample pipeline?)
- finish making every legacyTrees into a protected method so it can be overidden

@@ -10,9 +8,3 @@ - start documenting things addons do that are impossible to patch over

test behavior explicitly from test code.
- document how to use build-time-config to strip even static imports
- we need slightly stronger semantics for our code block stripping. It should also strip the corresponding import statements that are used only inside the stripped block. This means you're not allowed to rely on the side-effects of the import.
- next is tests.html entrypoint
- write README, publish to npm, open repos
- there's a weird babel bug that forced me to rename some locals in travis
- I removed livereload-inject package. Will need to decide if we want to keep control over livereload (in which case we should make it work) or whether that is a final-stage-packager concern (in which case our real requirement is to do nice fine-grained updates of the Workspace and let the packager take over from there).
- make sure we are following the same addon traversal order as ember-cli: https://github.com/ember-cli/ember-cli/pull/7979

@@ -22,1 +14,19 @@ - when adding tests, see https://github.com/stefanpenner/node-fixturify-project (as used in ember-cli)

# Introduce flags
allowNonSerializableASTPlugins: only downside today will be inability to run final stage in separate process. May want to parallelize though in future.
allowNonSerializableBabelPlugins: performance hit, this one is worth creating workarounds to reverse engineer plugins when possible and let people provide hints. May need new instrumentation around plugin registration in ember-cli
forceIncludeAddonTrees: force include everything from the addon trees. Eliminates need for some of our compat-adapters, though we would keep them anyway to help more people not need the flag.
forceIncludeComponents
forceIncludeHelpers (worth splitting out from components because there’s no “helper” helper)
# Vendor coodination
Vendor coordination: we can compile vendor files into a separate synthetic package, such that they are shared and deduplicated in a compatible way.
{
"name": "@embroider/core",
"version": "0.0.2",
"version": "0.0.3",
"description": "A build system for EmberJS applications.",

@@ -10,10 +10,14 @@ "main": "src/index.js",

"scripts": {
"prepare": "tsc"
"prepare": "tsc",
"test": "qunit tests"
},
"devDependencies": {
"@types/debug": "^0.0.31",
"@types/jsdom": "^12.2.0",
"@types/lodash": "^4.14.116",
"@types/node": "^10.5.2",
"@types/qunit": "^2.5.3",
"@types/resolve": "^0.0.8",
"@types/strip-bom": "^3.0.0",
"qunit": "^2.8.0",
"tslint": "^5.11.0",

@@ -23,2 +27,3 @@ "typescript": "^3.1.6"

"dependencies": {
"assert-never": "^1.1.0",
"broccoli-plugin": "^1.3.0",

@@ -28,7 +33,13 @@ "broccoli-source": "^1.1.0",

"fs-extra": "^7.0.1",
"fs-tree-diff": "0.5.7",
"handlebars": "^4.0.11",
"js-string-escape": "^1.0.1",
"jsdom": "^12.0.0",
"lodash": "^4.17.10",
"pkg-up": "^2.0.0",
"resolve": "^1.8.1",
"strip-bom": "^3.0.0",
"typescript-memoize": "^1.0.0-alpha.3"
"typescript-memoize": "^1.0.0-alpha.3",
"walk-sync": "^1.0.1"
}
}

@@ -1,5 +0,51 @@

import { Tree } from "broccoli-plugin";
export default interface App {
readonly tree: Tree;
readonly root: string;
import { OutputPaths } from './wait-for-trees';
import Package from './package';
import { Asset } from './asset';
export declare type EmberENV = unknown;
export interface AppAdapter<TreeNames> {
appJSSrcDir(treePaths: OutputPaths<TreeNames>): string;
assets(treePaths: OutputPaths<TreeNames>): Asset[];
autoRun(): boolean;
mainModule(): string;
mainModuleConfig(): unknown;
modulePrefix(): string;
extraDependencies?(): Package[];
templateCompilerSource(config: EmberENV): string;
babelConfig(finalRoot: string): {
config: {
plugins: (string | [string, any])[];
};
syntheticPlugins: Map<string, string>;
};
emberENV(): EmberENV;
externals(): string[];
}
export declare class AppBuilder<TreeNames> {
private root;
private app;
private adapter;
private assets;
constructor(root: string, app: Package, adapter: AppAdapter<TreeNames>);
private readonly activeAddonDescendants;
private scriptPriority;
private impliedAssets;
private impliedAddonAssets;
private readonly babelConfig;
private insertEmberApp;
private appDiffer;
private updateAppJS;
private prepareAsset;
private prepareAssets;
private updateOnDiskAsset;
private updateInMemoryAsset;
private updateBuiltEmberAsset;
private updateAssets;
build(inputPaths: OutputPaths<TreeNames>): Promise<void>;
private combineExternals;
private addTemplateCompiler;
private addBabelConfig;
private addEmberEnv;
private javascriptEntrypoint;
private testJSEntrypoint;
private gatherImplicitModules;
}
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const js_handlebars_1 = require("./js-handlebars");
const sortBy_1 = __importDefault(require("lodash/sortBy"));
const resolve_1 = __importDefault(require("resolve"));
const typescript_memoize_1 = require("typescript-memoize");
const fs_extra_1 = require("fs-extra");
const path_1 = require("path");
const messages_1 = require("./messages");
const cloneDeep_1 = __importDefault(require("lodash/cloneDeep"));
const app_differ_1 = __importDefault(require("./app-differ"));
const ember_html_1 = require("./ember-html");
const flatMap_1 = __importDefault(require("lodash/flatMap"));
const assert_never_1 = __importDefault(require("assert-never"));
class ParsedEmberAsset {
constructor(asset) {
this.kind = 'parsed-ember';
this.fileAsset = asset;
this.html = new ember_html_1.PreparedEmberHTML(asset);
this.relativePath = asset.relativePath;
}
validFor(other) {
return this.fileAsset.mtime === other.mtime && this.fileAsset.size === other.size;
}
}
class BuiltEmberAsset {
constructor(asset) {
this.kind = 'built-ember';
this.parsedAsset = asset;
this.source = asset.html.dom.serialize();
this.relativePath = asset.relativePath;
}
}
class AppBuilder {
constructor(root, app, adapter) {
this.root = root;
this.app = app;
this.adapter = adapter;
// for each relativePath, an Asset we have already emitted
this.assets = new Map();
}
get activeAddonDescendants() {
// todo: filter by addon-provided hook
let shouldInclude = (dep) => dep.isEmberPackage;
let result = this.app.findDescendants(shouldInclude);
if (this.adapter.extraDependencies) {
let extras = this.adapter.extraDependencies().filter(shouldInclude);
let extraDescendants = flatMap_1.default(extras, dep => dep.findDescendants(shouldInclude));
result = [...result, ...extras, ...extraDescendants];
}
return result;
}
scriptPriority(pkg) {
switch (pkg.name) {
case "loader.js":
return 0;
case "ember-source":
return 10;
default:
return 1000;
}
}
impliedAssets(type) {
let result = this.impliedAddonAssets(type);
// This file gets created by addEmberEnv(). We need to insert it at the
// beginning of the scripts.
if (type === "implicit-scripts") {
result.unshift(path_1.join(this.root, "_ember_env_.js"));
}
return result;
}
impliedAddonAssets(type) {
let result = [];
for (let addon of sortBy_1.default(this.activeAddonDescendants, this.scriptPriority.bind(this))) {
let implicitScripts = addon.meta[type];
if (implicitScripts) {
for (let mod of implicitScripts) {
result.push(resolve_1.default.sync(mod, { basedir: addon.root }));
}
}
}
return result;
}
get babelConfig() {
let rename = Object.assign({}, ...this.activeAddonDescendants.map(dep => dep.meta["renamed-modules"]));
let babel = this.adapter.babelConfig(this.root);
// this is our own plugin that patches up issues like non-explicit hbs
// extensions and packages importing their own names.
babel.config.plugins.push([require.resolve('./babel-plugin'), {
ownName: this.app.name,
basedir: this.root,
rename
}]);
return babel;
}
insertEmberApp(asset, appFiles, prepared) {
let appJS = prepared.get(`assets/${this.app.name}.js`);
if (!appJS) {
appJS = this.javascriptEntrypoint(this.app.name, appFiles);
prepared.set(appJS.relativePath, appJS);
}
let html = asset.html;
html.insertScriptTag(html.javascript, appJS.relativePath, { type: 'module' });
html.insertStyleLink(html.styles, `assets/${this.app.name}.css`);
for (let script of this.impliedAssets("implicit-scripts")) {
html.insertScriptTag(html.implicitScripts, path_1.relative(this.root, script));
}
for (let style of this.impliedAssets("implicit-styles")) {
html.insertStyleLink(html.implicitStyles, path_1.relative(this.root, style));
}
if (asset.fileAsset.includeTests) {
let testJS = prepared.get(`assets/test.js`);
if (!testJS) {
testJS = this.testJSEntrypoint(appFiles);
prepared.set(testJS.relativePath, testJS);
}
html.insertScriptTag(html.testJavascript, testJS.relativePath, { type: 'module' });
for (let script of this.impliedAssets("implicit-test-scripts")) {
html.insertScriptTag(html.implicitTestScripts, path_1.relative(this.root, script));
}
for (let style of this.impliedAssets("implicit-test-styles")) {
html.insertStyleLink(html.implicitTestStyles, path_1.relative(this.root, style));
}
}
}
updateAppJS(appJSPath) {
if (!this.appDiffer) {
this.appDiffer = new app_differ_1.default(this.root, appJSPath, this.activeAddonDescendants);
}
this.appDiffer.update();
return this.appDiffer.files;
}
prepareAsset(asset, appFiles, prepared) {
if (asset.kind === 'ember') {
let prior = this.assets.get(asset.relativePath);
let parsed;
if (prior && prior.kind === 'built-ember' && prior.parsedAsset.validFor(asset)) {
// we can reuse the parsed html
parsed = prior.parsedAsset;
parsed.html.clear();
}
else {
parsed = new ParsedEmberAsset(asset);
}
this.insertEmberApp(parsed, appFiles, prepared);
prepared.set(asset.relativePath, new BuiltEmberAsset(parsed));
}
else {
prepared.set(asset.relativePath, asset);
}
}
prepareAssets(requestedAssets, appFiles) {
let prepared = new Map();
for (let asset of requestedAssets) {
this.prepareAsset(asset, appFiles, prepared);
}
return prepared;
}
updateOnDiskAsset(asset, prior) {
if (prior && prior.kind === 'on-disk' && prior.size === asset.size && prior.mtime === asset.mtime) {
// prior was already valid
return;
}
let destination = path_1.join(this.root, asset.relativePath);
fs_extra_1.ensureDirSync(path_1.dirname(destination));
fs_extra_1.copySync(asset.sourcePath, destination, { dereference: true });
}
updateInMemoryAsset(asset, prior) {
if (prior && prior.kind === 'in-memory' && stringOrBufferEqual(prior.source, asset.source)) {
// prior was already valid
return;
}
let destination = path_1.join(this.root, asset.relativePath);
fs_extra_1.ensureDirSync(path_1.dirname(destination));
fs_extra_1.writeFileSync(destination, asset.source, "utf8");
}
updateBuiltEmberAsset(asset, prior) {
if (prior && prior.kind === 'built-ember' &&
prior.source === asset.source) {
// prior was already valid
return;
}
let destination = path_1.join(this.root, asset.relativePath);
fs_extra_1.ensureDirSync(path_1.dirname(destination));
fs_extra_1.writeFileSync(destination, asset.source, "utf8");
}
updateAssets(requestedAssets, appFiles) {
let assets = this.prepareAssets(requestedAssets, appFiles);
for (let asset of assets.values()) {
let prior = this.assets.get(asset.relativePath);
switch (asset.kind) {
case 'on-disk':
this.updateOnDiskAsset(asset, prior);
break;
case 'in-memory':
this.updateInMemoryAsset(asset, prior);
break;
case 'built-ember':
this.updateBuiltEmberAsset(asset, prior);
break;
default:
assert_never_1.default(asset);
}
}
for (let oldAsset of this.assets.values()) {
if (!assets.has(oldAsset.relativePath)) {
fs_extra_1.unlinkSync(path_1.join(this.root, oldAsset.relativePath));
}
}
this.assets = assets;
}
async build(inputPaths) {
let appFiles = this.updateAppJS(this.adapter.appJSSrcDir(inputPaths));
let emberENV = this.adapter.emberENV();
let assets = this.adapter.assets(inputPaths);
this.updateAssets(assets, appFiles);
this.addTemplateCompiler(emberENV);
this.addBabelConfig();
this.addEmberEnv(emberENV);
let externals = this.combineExternals();
let meta = {
version: 2,
externals,
assets: assets.map(a => a.relativePath),
["template-compiler"]: "_template_compiler_.js",
["babel-config"]: "_babel_config_.js",
};
let pkg = cloneDeep_1.default(this.app.packageJSON);
pkg["ember-addon"] = Object.assign({}, pkg["ember-addon"], meta);
fs_extra_1.writeFileSync(path_1.join(this.root, "package.json"), JSON.stringify(pkg, null, 2), "utf8");
}
combineExternals() {
let allAddonNames = new Set(this.activeAddonDescendants.map(d => d.name));
let externals = new Set();
for (let addon of this.activeAddonDescendants) {
if (!addon.meta.externals) {
continue;
}
for (let name of addon.meta.externals) {
if (allAddonNames.has(name)) {
messages_1.unsupported(`${addon.name} imports ${name} but does not directly depend on it.`);
}
else {
externals.add(name);
}
}
}
for (let name of this.adapter.externals()) {
if (allAddonNames.has(name)) {
messages_1.unsupported(`your app imports ${name} but does not directly depend on it.`);
}
else {
externals.add(name);
}
}
return [...externals.values()];
}
// we could just use ember-source/dist/ember-template-compiler directly, but
// apparently ember-cli adds some extra steps on top (like stripping BOM), so
// we follow along and do those too.
addTemplateCompiler(config) {
fs_extra_1.writeFileSync(path_1.join(this.root, "_template_compiler_.js"), this.adapter.templateCompilerSource(config), "utf8");
}
addBabelConfig() {
let { config, syntheticPlugins } = this.babelConfig;
for (let [name, source] of syntheticPlugins) {
let fullName = path_1.join(this.root, name);
fs_extra_1.writeFileSync(fullName, source, 'utf8');
let index = config.plugins.indexOf(name);
config.plugins[index] = fullName;
}
fs_extra_1.writeFileSync(path_1.join(this.root, "_babel_config_.js"), `
module.exports = ${JSON.stringify(config, null, 2)};
`, "utf8");
}
// this is stuff that needs to get set globally before Ember loads. In classic
// Ember CLI it was "vendor-prefix" content that would go at the start of the
// vendor.js. We are going to make sure it's the first plain <script> in the
// HTML that we hand to the final stage packager.
addEmberEnv(config) {
let content = `window.EmberENV=${JSON.stringify(config, null, 2)};`;
fs_extra_1.writeFileSync(path_1.join(this.root, "_ember_env_.js"), content, "utf8");
}
javascriptEntrypoint(name, appFiles) {
let modulePrefix = this.adapter.modulePrefix();
// for the app tree, we take everything
let lazyModules = [...appFiles].map(relativePath => {
if (!relativePath.startsWith('tests/') && (relativePath.endsWith('.js') || relativePath.endsWith('.hbs'))) {
let noJS = relativePath.replace(/\.js$/, "");
let noHBS = noJS.replace(/\.hbs$/, "");
return {
runtime: `${modulePrefix}/${noHBS}`,
buildtime: `../${noJS}`,
};
}
}).filter(Boolean);
// for the src tree, we can limit ourselves to only known resolvable
// collections
messages_1.todo("app src tree");
// this is a backward-compatibility feature: addons can force inclusion of
// modules.
this.gatherImplicitModules('implicit-modules', lazyModules);
let relativePath = `assets/${name}.js`;
let source = entryTemplate({
needsEmbroiderHook: true,
lazyModules,
autoRun: this.adapter.autoRun(),
mainModule: path_1.relative(path_1.dirname(relativePath), this.adapter.mainModule()),
appConfig: this.adapter.mainModuleConfig(),
});
return {
kind: 'in-memory',
source,
relativePath,
};
}
testJSEntrypoint(appFiles) {
let testModules = [...appFiles].map(relativePath => {
if (relativePath.startsWith("tests/") && relativePath.endsWith('-test.js')) {
return `../${relativePath}`;
}
}).filter(Boolean);
let lazyModules = [];
// this is a backward-compatibility feature: addons can force inclusion of
// test support modules.
this.gatherImplicitModules('implicit-test-modules', lazyModules);
let source = entryTemplate({
lazyModules,
eagerModules: testModules,
testSuffix: true
});
return {
kind: 'in-memory',
source,
relativePath: 'assets/test.js'
};
}
gatherImplicitModules(section, lazyModules) {
for (let addon of this.activeAddonDescendants) {
let implicitModules = addon.meta[section];
if (implicitModules) {
for (let name of implicitModules) {
lazyModules.push({
runtime: path_1.join(addon.name, name),
buildtime: path_1.relative(path_1.join(this.root, "assets"), path_1.join(addon.root, name)),
});
}
}
}
}
}
__decorate([
typescript_memoize_1.Memoize()
], AppBuilder.prototype, "activeAddonDescendants", null);
__decorate([
typescript_memoize_1.Memoize()
], AppBuilder.prototype, "babelConfig", null);
exports.AppBuilder = AppBuilder;
const entryTemplate = js_handlebars_1.compile(`
import { require as r } from '@embroider/core';
let w = window;
let d = w.define;
{{#if needsEmbroiderHook}}
{{!-
This function is the entrypoint that final stage packagers should
use to lookup externals at runtime.
-}}
w._embroider_ = function(specifier) {
let m;
if (specifier === 'require') {
m = w.require;
} else {
m = w.require(specifier);
}
{{!-
There are plenty of hand-written AMD defines floating around
that lack this, and they will break when other build systems
encounter them.
As far as I can tell, Ember's loader was already treating this
case as a module, so in theory we aren't breaking anything by
marking it as such when other packagers come looking.
todo: get review on this part.
-}}
if (m.default && !m.__esModule) {
m.__esModule = true;
}
return m;
};
{{/if}}
{{#each eagerModules as |eagerModule| ~}}
import "{{js-string-escape eagerModule}}";
{{/each}}
{{#each lazyModules as |lazyModule| ~}}
d("{{js-string-escape lazyModule.runtime}}", function(){ return r("{{js-string-escape lazyModule.buildtime}}");});
{{/each}}
{{#if autoRun ~}}
r("{{js-string-escape mainModule}}").default.create({{{json-stringify appConfig}}});
{{/if}}
{{#if testSuffix ~}}
{{!- this is the traditional tests-suffix.js -}}
r('../tests/test-helper');
EmberENV.TESTS_FILE_LOADED = true;
{{/if}}
`);
function stringOrBufferEqual(a, b) {
if (typeof a === 'string' && typeof b === 'string') {
return a === b;
}
if (a instanceof Buffer && b instanceof Buffer) {
return Buffer.compare(a, b) === 0;
}
return false;
}
//# sourceMappingURL=app.js.map

@@ -1,11 +0,568 @@

import { Tree } from "broccoli-plugin";
import { AppMeta } from './metadata';
import { OutputPaths } from './wait-for-trees';
import { compile } from './js-handlebars';
import Package from './package';
import sortBy from 'lodash/sortBy';
import resolve from 'resolve';
import { Memoize } from "typescript-memoize";
import { writeFileSync, ensureDirSync, copySync, unlinkSync } from 'fs-extra';
import { join, dirname, relative } from 'path';
import { todo, unsupported } from './messages';
import cloneDeep from 'lodash/cloneDeep';
import AppDiffer from './app-differ';
import { PreparedEmberHTML } from './ember-html';
import { Asset, EmberAsset, InMemoryAsset, OnDiskAsset, ImplicitAssetPaths } from './asset';
import flatMap from 'lodash/flatMap';
import assertNever from 'assert-never';
export default interface App {
// this is the broccoli tree that must get built for the app to be ready. But!
// This tree's output path is _not_ necessarily where the final app will be,
// for that you must look at `root`.
readonly tree: Tree;
export type EmberENV = unknown;
// This is the actual directory in which the app will be.
readonly root: string;
/*
This interface is the boundary between the general-purpose build system in
AppBuilder and the messy specifics of apps.
- CompatAppAdapter in `@embroider/compat` implements this interface for
building based of a legacy ember-cli EmberApp instance
- We will want to make a different class that implmenets this interface for
building apps that don't need an EmberApp instance at all (presumably
because they opt into new authoring standards.
*/
export interface AppAdapter<TreeNames> {
// path to the directory where the app's own Javascript lives. Doesn't include
// any files copied out of addons, we take care of that generically in
// AppBuilder.
appJSSrcDir(treePaths: OutputPaths<TreeNames>): string;
// this is where you declare what assets must be in the final output
// (especially index.html, tests/index.html, and anything from your classic
// public tree).
assets(treePaths: OutputPaths<TreeNames>): Asset[];
// whether the ember app should boot itself automatically
autoRun(): boolean;
// the ember app's main module
mainModule(): string;
// the configuration that will get passed into the ember app's main module.
// This traditionally comes from the `APP` property returned by
// config/environment.js.
mainModuleConfig(): unknown;
// The namespace for the app's own modules at runtime.
//
// (For apps, we _do_ still allow this to be arbitrary. This is in contrast
// with _addons_, which absolutley must use their real NPM package name as
// their modulePrefix.)
modulePrefix(): string;
// optional method to force extra packages to be treated as dependencies of
// the app.
extraDependencies?(): Package[];
// this is actual Javascript for a module that provides template compilation.
// See how CompatAppAdapter does it for an example.
templateCompilerSource(config: EmberENV): string;
// this lets us figure out the babel config used by the app. You receive
// "finalRoot" which is where the app will be when we run babel against it,
// and you must make sure that the configuration will resolve correctly from
// that path.
//
// - `config` is the actual babel configuration object.
// - `syntheticPlugins` is a map from plugin names to Javascript source code
// for babel plugins. This can make it possible to serialize babel
// configs that would otherwise not be serializable.
babelConfig(finalRoot: string): { config: { plugins: (string | [string,any])[]}, syntheticPlugins: Map<string, string> };
// The environment settings used to control Ember itself. In a classic app,
// this comes from the EmberENV property returned by config/environment.js.
emberENV(): EmberENV;
// the list of module specifiers that are used in the app that are not
// resolvable at build time. This is how we figure out the "externals" for the
// app itself as defined in SPEC.md.
externals(): string[];
}
class ParsedEmberAsset {
kind: 'parsed-ember' = 'parsed-ember';
relativePath: string;
fileAsset: EmberAsset;
html: PreparedEmberHTML;
constructor(asset: EmberAsset) {
this.fileAsset = asset;
this.html = new PreparedEmberHTML(asset);
this.relativePath = asset.relativePath;
}
validFor(other: EmberAsset) {
return this.fileAsset.mtime === other.mtime && this.fileAsset.size === other.size;
}
}
class BuiltEmberAsset {
kind: 'built-ember' = 'built-ember';
relativePath: string;
parsedAsset: ParsedEmberAsset;
source: string;
constructor(asset: ParsedEmberAsset) {
this.parsedAsset = asset;
this.source = asset.html.dom.serialize();
this.relativePath = asset.relativePath;
}
}
type InternalAsset = OnDiskAsset | InMemoryAsset | BuiltEmberAsset;
export class AppBuilder<TreeNames> {
// for each relativePath, an Asset we have already emitted
private assets: Map<string, InternalAsset> = new Map();
constructor(
private root: string,
private app: Package,
private adapter: AppAdapter<TreeNames>
) {}
@Memoize()
private get activeAddonDescendants(): Package[] {
// todo: filter by addon-provided hook
let shouldInclude = (dep: Package) => dep.isEmberPackage;
let result = this.app.findDescendants(shouldInclude);
if (this.adapter.extraDependencies) {
let extras = this.adapter.extraDependencies().filter(shouldInclude);
let extraDescendants = flatMap(extras, dep => dep.findDescendants(shouldInclude));
result = [...result, ...extras, ...extraDescendants];
}
return result;
}
private scriptPriority(pkg: Package) {
switch (pkg.name) {
case "loader.js":
return 0;
case "ember-source":
return 10;
default:
return 1000;
}
}
private impliedAssets(type: keyof ImplicitAssetPaths): any {
let result = this.impliedAddonAssets(type);
// This file gets created by addEmberEnv(). We need to insert it at the
// beginning of the scripts.
if (type === "implicit-scripts") {
result.unshift(join(this.root, "_ember_env_.js"));
}
return result;
}
private impliedAddonAssets(type: keyof ImplicitAssetPaths): any {
let result = [];
for (let addon of sortBy(
this.activeAddonDescendants,
this.scriptPriority.bind(this)
)) {
let implicitScripts = addon.meta[type];
if (implicitScripts) {
for (let mod of implicitScripts) {
result.push(resolve.sync(mod, { basedir: addon.root }));
}
}
}
return result;
}
@Memoize()
private get babelConfig() {
let rename = Object.assign(
{},
...this.activeAddonDescendants.map(dep => dep.meta["renamed-modules"])
);
let babel = this.adapter.babelConfig(this.root);
// this is our own plugin that patches up issues like non-explicit hbs
// extensions and packages importing their own names.
babel.config.plugins.push([require.resolve('./babel-plugin'), {
ownName: this.app.name,
basedir: this.root,
rename
}]);
return babel;
}
private insertEmberApp(asset: ParsedEmberAsset, appFiles: Set<string>, prepared: Map<string, InternalAsset>) {
let appJS = prepared.get(`assets/${this.app.name}.js`);
if (!appJS) {
appJS = this.javascriptEntrypoint(this.app.name, appFiles);
prepared.set(appJS.relativePath, appJS);
}
let html = asset.html;
html.insertScriptTag(html.javascript, appJS.relativePath, { type: 'module' });
html.insertStyleLink(html.styles, `assets/${this.app.name}.css`);
for (let script of this.impliedAssets("implicit-scripts")) {
html.insertScriptTag(html.implicitScripts, relative(this.root, script));
}
for (let style of this.impliedAssets("implicit-styles")) {
html.insertStyleLink(html.implicitStyles, relative(this.root, style));
}
if (asset.fileAsset.includeTests) {
let testJS = prepared.get(`assets/test.js`);
if (!testJS) {
testJS = this.testJSEntrypoint(appFiles);
prepared.set(testJS.relativePath, testJS);
}
html.insertScriptTag(html.testJavascript, testJS.relativePath, { type: 'module' });
for (let script of this.impliedAssets("implicit-test-scripts")) {
html.insertScriptTag(html.implicitTestScripts, relative(this.root, script));
}
for (let style of this.impliedAssets("implicit-test-styles")) {
html.insertStyleLink(html.implicitTestStyles, relative(this.root, style));
}
}
}
private appDiffer: AppDiffer | undefined;
private updateAppJS(appJSPath: string): Set<string> {
if (!this.appDiffer) {
this.appDiffer = new AppDiffer(this.root, appJSPath, this.activeAddonDescendants);
}
this.appDiffer.update();
return this.appDiffer.files;
}
private prepareAsset(asset: Asset, appFiles: Set<string>, prepared: Map<string, InternalAsset>) {
if (asset.kind === 'ember') {
let prior = this.assets.get(asset.relativePath);
let parsed: ParsedEmberAsset;
if (prior && prior.kind === 'built-ember' && prior.parsedAsset.validFor(asset)) {
// we can reuse the parsed html
parsed = prior.parsedAsset;
parsed.html.clear();
} else {
parsed = new ParsedEmberAsset(asset);
}
this.insertEmberApp(parsed, appFiles, prepared);
prepared.set(asset.relativePath, new BuiltEmberAsset(parsed));
} else {
prepared.set(asset.relativePath, asset);
}
}
private prepareAssets(requestedAssets: Asset[], appFiles: Set<string>): Map<string, InternalAsset> {
let prepared: Map<string, InternalAsset> = new Map();
for (let asset of requestedAssets) {
this.prepareAsset(asset, appFiles, prepared);
}
return prepared;
}
private updateOnDiskAsset(asset: OnDiskAsset, prior: InternalAsset | undefined) {
if (prior && prior.kind === 'on-disk' && prior.size === asset.size && prior.mtime === asset.mtime) {
// prior was already valid
return;
}
let destination = join(this.root, asset.relativePath);
ensureDirSync(dirname(destination));
copySync(asset.sourcePath, destination, { dereference: true });
}
private updateInMemoryAsset(asset: InMemoryAsset, prior: InternalAsset | undefined) {
if (prior && prior.kind === 'in-memory' && stringOrBufferEqual(prior.source, asset.source)) {
// prior was already valid
return;
}
let destination = join(this.root, asset.relativePath);
ensureDirSync(dirname(destination));
writeFileSync(destination, asset.source, "utf8");
}
private updateBuiltEmberAsset(asset: BuiltEmberAsset, prior: InternalAsset | undefined) {
if (
prior && prior.kind === 'built-ember' &&
prior.source === asset.source
) {
// prior was already valid
return;
}
let destination = join(this.root, asset.relativePath);
ensureDirSync(dirname(destination));
writeFileSync(destination, asset.source, "utf8");
}
private updateAssets(requestedAssets: Asset[], appFiles: Set<string>) {
let assets = this.prepareAssets(requestedAssets, appFiles);
for (let asset of assets.values()) {
let prior = this.assets.get(asset.relativePath);
switch (asset.kind) {
case 'on-disk':
this.updateOnDiskAsset(asset, prior);
break;
case 'in-memory':
this.updateInMemoryAsset(asset, prior);
break;
case 'built-ember':
this.updateBuiltEmberAsset(asset, prior);
break;
default:
assertNever(asset);
}
}
for (let oldAsset of this.assets.values()) {
if (!assets.has(oldAsset.relativePath)) {
unlinkSync(join(this.root, oldAsset.relativePath));
}
}
this.assets = assets;
}
async build(inputPaths: OutputPaths<TreeNames>) {
let appFiles = this.updateAppJS(this.adapter.appJSSrcDir(inputPaths));
let emberENV = this.adapter.emberENV();
let assets = this.adapter.assets(inputPaths);
this.updateAssets(assets, appFiles);
this.addTemplateCompiler(emberENV);
this.addBabelConfig();
this.addEmberEnv(emberENV);
let externals = this.combineExternals();
let meta: AppMeta = {
version: 2,
externals,
assets: assets.map(a => a.relativePath),
["template-compiler"]: "_template_compiler_.js",
["babel-config"]: "_babel_config_.js",
};
let pkg = cloneDeep(this.app.packageJSON);
pkg["ember-addon"] = Object.assign({}, pkg["ember-addon"], meta);
writeFileSync(
join(this.root, "package.json"),
JSON.stringify(pkg, null, 2),
"utf8"
);
}
private combineExternals() {
let allAddonNames = new Set(this.activeAddonDescendants.map(d => d.name));
let externals = new Set();
for (let addon of this.activeAddonDescendants) {
if (!addon.meta.externals) {
continue;
}
for (let name of addon.meta.externals) {
if (allAddonNames.has(name)) {
unsupported(`${addon.name} imports ${name} but does not directly depend on it.`);
} else {
externals.add(name);
}
}
}
for (let name of this.adapter.externals()) {
if (allAddonNames.has(name)) {
unsupported(`your app imports ${name} but does not directly depend on it.`);
} else {
externals.add(name);
}
}
return [...externals.values()];
}
// we could just use ember-source/dist/ember-template-compiler directly, but
// apparently ember-cli adds some extra steps on top (like stripping BOM), so
// we follow along and do those too.
private addTemplateCompiler(config: EmberENV) {
writeFileSync(
join(this.root, "_template_compiler_.js"),
this.adapter.templateCompilerSource(config),
"utf8"
);
}
private addBabelConfig() {
let { config, syntheticPlugins } = this.babelConfig;
for (let [name, source] of syntheticPlugins) {
let fullName = join(this.root, name);
writeFileSync(fullName, source, 'utf8');
let index = config.plugins.indexOf(name);
config.plugins[index] = fullName;
}
writeFileSync(
join(this.root, "_babel_config_.js"),
`
module.exports = ${JSON.stringify(config, null, 2)};
`,
"utf8"
);
}
// this is stuff that needs to get set globally before Ember loads. In classic
// Ember CLI it was "vendor-prefix" content that would go at the start of the
// vendor.js. We are going to make sure it's the first plain <script> in the
// HTML that we hand to the final stage packager.
private addEmberEnv(config: EmberENV) {
let content = `window.EmberENV=${JSON.stringify(config, null, 2)};`;
writeFileSync(join(this.root, "_ember_env_.js"), content, "utf8");
}
private javascriptEntrypoint(name: string, appFiles: Set<string>): InternalAsset {
let modulePrefix = this.adapter.modulePrefix();
// for the app tree, we take everything
let lazyModules = [...appFiles].map(relativePath => {
if (!relativePath.startsWith('tests/') && (relativePath.endsWith('.js') || relativePath.endsWith('.hbs'))) {
let noJS = relativePath.replace(/\.js$/, "");
let noHBS = noJS.replace(/\.hbs$/, "");
return {
runtime: `${modulePrefix}/${noHBS}`,
buildtime: `../${noJS}`,
};
}
}).filter(Boolean) as { runtime: string, buildtime: string }[];
// for the src tree, we can limit ourselves to only known resolvable
// collections
todo("app src tree");
// this is a backward-compatibility feature: addons can force inclusion of
// modules.
this.gatherImplicitModules('implicit-modules', lazyModules);
let relativePath = `assets/${name}.js`;
let source = entryTemplate({
needsEmbroiderHook: true,
lazyModules,
autoRun: this.adapter.autoRun(),
mainModule: relative(dirname(relativePath), this.adapter.mainModule()),
appConfig: this.adapter.mainModuleConfig(),
});
return {
kind: 'in-memory',
source,
relativePath,
};
}
private testJSEntrypoint(appFiles: Set<string>): InternalAsset {
let testModules = [...appFiles].map(relativePath => {
if (relativePath.startsWith("tests/") && relativePath.endsWith('-test.js')) {
return `../${relativePath}`;
}
}).filter(Boolean) as string[];
let lazyModules: { runtime: string, buildtime: string }[] = [];
// this is a backward-compatibility feature: addons can force inclusion of
// test support modules.
this.gatherImplicitModules('implicit-test-modules', lazyModules);
let source = entryTemplate({
lazyModules,
eagerModules: testModules,
testSuffix: true
});
return {
kind: 'in-memory',
source,
relativePath: 'assets/test.js'
};
}
private gatherImplicitModules(section: "implicit-modules" | "implicit-test-modules", lazyModules: { runtime: string, buildtime: string }[]) {
for (let addon of this.activeAddonDescendants) {
let implicitModules = addon.meta[section];
if (implicitModules) {
for (let name of implicitModules) {
lazyModules.push({
runtime: join(addon.name, name),
buildtime: relative(
join(this.root, "assets"),
join(addon.root, name)
),
});
}
}
}
}
}
const entryTemplate = compile(`
import { require as r } from '@embroider/core';
let w = window;
let d = w.define;
{{#if needsEmbroiderHook}}
{{!-
This function is the entrypoint that final stage packagers should
use to lookup externals at runtime.
-}}
w._embroider_ = function(specifier) {
let m;
if (specifier === 'require') {
m = w.require;
} else {
m = w.require(specifier);
}
{{!-
There are plenty of hand-written AMD defines floating around
that lack this, and they will break when other build systems
encounter them.
As far as I can tell, Ember's loader was already treating this
case as a module, so in theory we aren't breaking anything by
marking it as such when other packagers come looking.
todo: get review on this part.
-}}
if (m.default && !m.__esModule) {
m.__esModule = true;
}
return m;
};
{{/if}}
{{#each eagerModules as |eagerModule| ~}}
import "{{js-string-escape eagerModule}}";
{{/each}}
{{#each lazyModules as |lazyModule| ~}}
d("{{js-string-escape lazyModule.runtime}}", function(){ return r("{{js-string-escape lazyModule.buildtime}}");});
{{/each}}
{{#if autoRun ~}}
r("{{js-string-escape mainModule}}").default.create({{{json-stringify appConfig}}});
{{/if}}
{{#if testSuffix ~}}
{{!- this is the traditional tests-suffix.js -}}
r('../tests/test-helper');
EmberENV.TESTS_FILE_LOADED = true;
{{/if}}
`);
function stringOrBufferEqual(a: string | Buffer, b: string | Buffer): boolean {
if (typeof a === 'string' && typeof b === 'string') {
return a === b;
}
if (a instanceof Buffer && b instanceof Buffer) {
return Buffer.compare(a,b) === 0;
}
return false;
}

@@ -20,3 +20,3 @@ "use strict";

this.packageCache = packageCache;
this.dependencyKeys = mayUseDevDeps ? ['dependencies', 'devDependencies'] : ['dependencies'];
this.dependencyKeys = mayUseDevDeps ? ['dependencies', 'devDependencies', 'peerDependencies'] : ['dependencies', 'peerDependencies'];
}

@@ -23,0 +23,0 @@ get dependencies() {

@@ -7,3 +7,3 @@ import { Memoize } from 'typescript-memoize';

export default class BasicPackage extends Package {
private dependencyKeys: ("dependencies" | "devDependencies")[];
private dependencyKeys: ("dependencies" | "devDependencies" | "peerDependencies")[];

@@ -16,3 +16,3 @@ constructor(

super();
this.dependencyKeys = mayUseDevDeps ? ['dependencies', 'devDependencies'] : ['dependencies'];
this.dependencyKeys = mayUseDevDeps ? ['dependencies', 'devDependencies', 'peerDependencies'] : ['dependencies', 'peerDependencies'];
}

@@ -19,0 +19,0 @@

export { Packager, PackagerInstance } from './packager';
export { AppMeta, AddonMeta } from './metadata';
export { default as App } from './app';
export { default as Package } from './package';
export { default as Workspace } from './workspace';
export { default as Stage } from './stage';
export { Compiler as TemplateCompiler, Plugins as TemplateCompilerPlugins } from './template-compiler';
export { AppAdapter, AppBuilder, EmberENV } from './app';
export { Asset, EmberAsset, ImplicitAssetPaths } from './asset';
export { default as toBroccoliPlugin } from './to-broccoli-plugin';
export { default as PrebuiltWorkspace } from './prebuilt-workspace';
export { default as WorkspaceUpdater } from './workspace-updater';
export { default as PrebuiltAddons } from './prebuilt-addons';
export { default as PackageCache } from './package-cache';
export { default as packageName } from './package-name';
export { default as BasicPackage } from './basic-package';
export { default as WaitForTrees, OutputPaths } from './wait-for-trees';
export { default as BuildStage } from './build-stage';
export { getOrCreate } from './get-or-create';
export { compile as jsHandlebarsCompile } from './js-handlebars';

@@ -5,9 +5,9 @@ "use strict";

exports.Package = package_1.default;
var app_1 = require("./app");
exports.AppBuilder = app_1.AppBuilder;
// Shared utilities
var to_broccoli_plugin_1 = require("./to-broccoli-plugin");
exports.toBroccoliPlugin = to_broccoli_plugin_1.default;
var prebuilt_workspace_1 = require("./prebuilt-workspace");
exports.PrebuiltWorkspace = prebuilt_workspace_1.default;
var workspace_updater_1 = require("./workspace-updater");
exports.WorkspaceUpdater = workspace_updater_1.default;
var prebuilt_addons_1 = require("./prebuilt-addons");
exports.PrebuiltAddons = prebuilt_addons_1.default;
var package_cache_1 = require("./package-cache");

@@ -19,4 +19,10 @@ exports.PackageCache = package_cache_1.default;

exports.BasicPackage = basic_package_1.default;
var wait_for_trees_1 = require("./wait-for-trees");
exports.WaitForTrees = wait_for_trees_1.default;
var build_stage_1 = require("./build-stage");
exports.BuildStage = build_stage_1.default;
var get_or_create_1 = require("./get-or-create");
exports.getOrCreate = get_or_create_1.getOrCreate;
var js_handlebars_1 = require("./js-handlebars");
exports.jsHandlebarsCompile = js_handlebars_1.compile;
//# sourceMappingURL=index.js.map
// Shared interfaces
export { Packager, PackagerInstance } from './packager';
export { AppMeta, AddonMeta } from './metadata';
export { default as App } from './app';
export { default as Package } from './package';
export { default as Workspace } from './workspace';
export { default as Stage } from './stage';
export { Compiler as TemplateCompiler, Plugins as TemplateCompilerPlugins } from './template-compiler';
export { AppAdapter, AppBuilder, EmberENV } from './app';
export { Asset, EmberAsset, ImplicitAssetPaths } from './asset';
// Shared utilities
export { default as toBroccoliPlugin } from './to-broccoli-plugin';
export { default as PrebuiltWorkspace } from './prebuilt-workspace';
export { default as WorkspaceUpdater } from './workspace-updater';
export { default as PrebuiltAddons } from './prebuilt-addons';
export { default as PackageCache } from './package-cache';
export { default as packageName } from './package-name';
export { default as BasicPackage } from './basic-package';
export { default as WaitForTrees, OutputPaths } from './wait-for-trees';
export { default as BuildStage } from './build-stage';
export { getOrCreate } from './get-or-create';
export { compile as jsHandlebarsCompile } from './js-handlebars';
import makeDebug from 'debug';
declare const todo: makeDebug.IDebugger;
declare const unsupported: makeDebug.IDebugger;
export { todo, unsupported };
declare const debug: makeDebug.IDebugger;
export { todo, unsupported, debug };

@@ -11,2 +11,4 @@ "use strict";

exports.unsupported = unsupported;
const debug = debug_1.default('embroider:core:debug');
exports.debug = debug;
//# sourceMappingURL=messages.js.map
import makeDebug from 'debug';
const todo = makeDebug('embroider:core:todo');
const unsupported = makeDebug('embroider:core:unsupported');
const debug = makeDebug('embroider:core:debug');
export { todo, unsupported };
export { todo, unsupported, debug };
declare type filename = string;
export interface AppMeta {
version: 2;
entrypoints: filename[];
assets: filename[];
externals?: string[];

@@ -17,2 +17,3 @@ "template-compiler": filename;

"implicit-modules"?: string[];
"implicit-test-modules"?: string[];
"renamed-modules"?: {

@@ -19,0 +20,0 @@ [fromName: string]: string;

@@ -7,3 +7,3 @@ type filename = string;

version: 2;
entrypoints: filename[];
assets: filename[];
externals?: string[];

@@ -24,4 +24,5 @@ "template-compiler": filename;

"implicit-modules"?: string[];
"implicit-test-modules"?: string[];
"renamed-modules"?: { [fromName: string]: string };
"app-js"?: filename;
}

@@ -6,6 +6,7 @@ import Package from './package';

protected rootCache: Map<string, Package>;
protected resolutionCache: WeakMap<Package, Map<string, Package>>;
protected resolutionCache: Map<Package, Map<string, Package>>;
protected basedir(pkg: Package): string;
private getPackage;
private getAddon;
getAddon(packageRoot: string): Package;
ownerOfFile(filename: string): Package | undefined;
}

@@ -11,6 +11,7 @@ "use strict";

const path_1 = require("path");
const pkg_up_1 = require("pkg-up");
class PackageCache {
constructor() {
this.rootCache = new Map();
this.resolutionCache = new WeakMap();
this.resolutionCache = new Map();
}

@@ -40,4 +41,24 @@ resolve(packageName, fromPackage) {

}
ownerOfFile(filename) {
let segments = filename.split('/');
// first we look through our cached packages for any that are rooted right
// at or above the file.
for (let length = segments.length - 1; length >= 0; length--) {
if (segments[length - 1] === 'node_modules') {
// once we hit a node_modules, we're leaving the package we were in, so
// any higher caches don't apply to us
break;
}
let candidate = segments.slice(0, length).join('/');
if (this.rootCache.has(candidate)) {
return this.rootCache.get(candidate);
}
}
let packageJSONPath = pkg_up_1.sync(filename);
if (packageJSONPath) {
return this.getAddon(path_1.dirname(packageJSONPath));
}
}
}
exports.default = PackageCache;
//# sourceMappingURL=package-cache.js.map

@@ -7,2 +7,3 @@ import BasicPackage from "./basic-package";

import { join, dirname } from 'path';
import { sync as pkgUpSync } from 'pkg-up';

@@ -23,3 +24,3 @@ export default class PackageCache {

protected rootCache: Map<string, Package> = new Map();
protected resolutionCache: WeakMap<Package, Map<string, Package>> = new WeakMap();
protected resolutionCache: Map<Package, Map<string, Package>> = new Map();

@@ -38,6 +39,29 @@ protected basedir(pkg: Package): string {

private getAddon(packageRoot: string) {
getAddon(packageRoot: string) {
return this.getPackage(packageRoot, true);
}
ownerOfFile(filename: string): Package | undefined {
let segments = filename.split('/');
// first we look through our cached packages for any that are rooted right
// at or above the file.
for (let length = segments.length - 1; length >= 0; length--) {
if (segments[length-1] === 'node_modules') {
// once we hit a node_modules, we're leaving the package we were in, so
// any higher caches don't apply to us
break;
}
let candidate = segments.slice(0, length).join('/');
if (this.rootCache.has(candidate)) {
return this.rootCache.get(candidate);
}
}
let packageJSONPath = pkgUpSync(filename);
if (packageJSONPath) {
return this.getAddon(dirname(packageJSONPath));
}
}
}

@@ -13,2 +13,3 @@ import { AddonMeta } from './metadata';

findDescendants(filter?: (pkg: Package) => boolean): Package[];
readonly mayRebuild: boolean;
}

@@ -60,2 +60,7 @@ "use strict";

}
// by default, addons do not get rebuilt on the fly. This can be changed when
// you are actively developing one.
get mayRebuild() {
return false;
}
}

@@ -62,0 +67,0 @@ __decorate([

@@ -62,2 +62,8 @@ import { Memoize } from 'typescript-memoize';

}
// by default, addons do not get rebuilt on the fly. This can be changed when
// you are actively developing one.
get mayRebuild(): boolean {
return false;
}
}

@@ -0,3 +1,5 @@

import PackageCache from "./package-cache";
export interface Packager<Options> {
new (inputPath: string, outputPath: string, consoleWrite: (message: string) => void, options?: Options): PackagerInstance;
new (inputPath: string, outputPath: string, consoleWrite: (message: string) => void, packageCache: PackageCache, options?: Options): PackagerInstance;
annotation: string;
}

@@ -4,0 +6,0 @@ export interface PackagerInstance {

@@ -0,8 +1,31 @@

import PackageCache from "./package-cache";
export interface Packager<Options> {
new (
// where on disk the packager will find the app it's supposed to build. The
// app and its addons will necessarily already be in v2 format, which is
// what makes a Packager a cleanly separable stage that needs only a small
// amount of ember-specific knowledge.
inputPath: string,
// where the packager should write the packaged app.
outputPath: string,
// if possible, the packager should direct its console output through this
// hook.
consoleWrite: (message: string) => void,
// the packager is free to take advantage of this shared PackageCache
// instance as an optimization, since it might want to know things that have
// already been discovered by the earlier build stages.
packageCache: PackageCache,
// A packager can have whatever custom options type it wants here. If the
// packager is based on a third-party tool, this is where that tool's
// configuration can go.
options?: Options,
): PackagerInstance;
// a description for this packager that aids debugging & profiling
annotation: string;
}

@@ -9,0 +32,0 @@

@@ -1,3 +0,9 @@

export default function (compiler: {
precompile: any;
}): (moduleName: string, contents: string) => string;
export interface Plugins {
[type: string]: unknown[];
}
export interface Compiler {
precompile(templateContents: string, options: any): string;
registerPlugin(type: string, plugin: unknown): void;
_Ember: any;
}
export default function (compiler: Compiler, EmberENV: unknown, plugins: Plugins): (moduleName: string, contents: string) => string;

@@ -7,3 +7,5 @@ "use strict";

const strip_bom_1 = __importDefault(require("strip-bom"));
function default_1(compiler) {
function default_1(compiler, EmberENV, plugins) {
registerPlugins(compiler, plugins);
initializeEmberENV(compiler, EmberENV);
return function (moduleName, contents) {

@@ -18,2 +20,30 @@ let compiled = compiler.precompile(strip_bom_1.default(contents), {

exports.default = default_1;
function registerPlugins(compiler, plugins) {
for (let type in plugins) {
for (let i = 0, l = plugins[type].length; i < l; i++) {
compiler.registerPlugin(type, plugins[type][i]);
}
}
}
function initializeEmberENV(templateCompiler, EmberENV) {
if (!templateCompiler || !EmberENV) {
return;
}
let props;
if (EmberENV.FEATURES) {
props = Object.keys(EmberENV.FEATURES);
props.forEach(prop => {
templateCompiler._Ember.FEATURES[prop] = EmberENV.FEATURES[prop];
});
}
if (EmberENV) {
props = Object.keys(EmberENV);
props.forEach(prop => {
if (prop === 'FEATURES') {
return;
}
templateCompiler._Ember.ENV[prop] = EmberENV[prop];
});
}
}
//# sourceMappingURL=template-compiler.js.map
import stripBom from 'strip-bom';
export default function(compiler: { precompile: any }) {
export interface Plugins {
[type: string]: unknown[];
}
export interface Compiler {
precompile(templateContents: string, options: any): string;
registerPlugin(type: string, plugin: unknown): void;
_Ember: any;
}
export default function(compiler: Compiler, EmberENV: unknown, plugins: Plugins) {
registerPlugins(compiler, plugins);
initializeEmberENV(compiler, EmberENV);
return function(moduleName: string, contents: string) {

@@ -14,1 +26,33 @@ let compiled = compiler.precompile(

}
function registerPlugins(compiler: Compiler, plugins: Plugins) {
for (let type in plugins) {
for (let i = 0, l = plugins[type].length; i < l; i++) {
compiler.registerPlugin(type, plugins[type][i]);
}
}
}
function initializeEmberENV(templateCompiler: Compiler, EmberENV: any) {
if (!templateCompiler || !EmberENV) { return; }
let props;
if (EmberENV.FEATURES) {
props = Object.keys(EmberENV.FEATURES);
props.forEach(prop => {
templateCompiler._Ember.FEATURES[prop] = EmberENV.FEATURES[prop];
});
}
if (EmberENV) {
props = Object.keys(EmberENV);
props.forEach(prop => {
if (prop === 'FEATURES') { return; }
templateCompiler._Ember.ENV[prop] = EmberENV[prop];
});
}
}
import Plugin from "broccoli-plugin";
import { Packager } from "./packager";
import App from "./app";
import Stage from "./stage";
interface BroccoliPackager<Options> {
new (app: App, options?: Options): Plugin;
new (stage: Stage, options?: Options): Plugin;
}
export default function toBroccoliPlugin<Options>(packagerClass: Packager<Options>): BroccoliPackager<Options>;
export {};
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importDefault = (this && this.__importDefault) || function (mod) {

@@ -13,23 +7,28 @@ return (mod && mod.__esModule) ? mod : { "default": mod };

const broccoli_plugin_1 = __importDefault(require("broccoli-plugin"));
const typescript_memoize_1 = require("typescript-memoize");
const package_cache_1 = __importDefault(require("./package-cache"));
function toBroccoliPlugin(packagerClass) {
class PackagerRunner extends broccoli_plugin_1.default {
constructor(app, options) {
super([app.tree], {
constructor(stage, options) {
super([stage.tree], {
persistentOutput: true,
needsCache: false
needsCache: false,
annotation: packagerClass.annotation
});
this.app = app;
this.stage = stage;
this.options = options;
}
get packager() {
return new packagerClass(this.app.root, this.outputPath, (msg) => console.log(msg), this.options);
}
build() {
async build() {
if (!this.packager) {
let { outputPath, packageCache } = await this.stage.ready();
if (!packageCache) {
// stages are allowed to share a package cache as an optimization, but
// they aren't required to. Whereas Packages are allowed to assume
// they will receive a packageCache instance.
packageCache = new package_cache_1.default();
}
this.packager = new packagerClass(outputPath, this.outputPath, (msg) => console.log(msg), packageCache, this.options);
}
return this.packager.build();
}
}
__decorate([
typescript_memoize_1.Memoize()
], PackagerRunner.prototype, "packager", null);
return PackagerRunner;

@@ -36,0 +35,0 @@ }

import Plugin from "broccoli-plugin";
import { Packager, PackagerInstance } from "./packager";
import { Memoize } from "typescript-memoize";
import App from "./app";
import Stage from "./stage";
import PackageCache from "./package-cache";
interface BroccoliPackager<Options> {
new(app: App, options?: Options): Plugin;
new(stage: Stage, options?: Options): Plugin;
}

@@ -12,20 +12,28 @@

class PackagerRunner extends Plugin {
constructor(private app: App, private options?: Options) {
super([app.tree], {
private packager: PackagerInstance | undefined;
constructor(private stage: Stage, private options?: Options) {
super([stage.tree], {
persistentOutput: true,
needsCache: false
needsCache: false,
annotation: packagerClass.annotation
});
}
@Memoize()
private get packager(): PackagerInstance {
return new packagerClass(
this.app.root,
this.outputPath,
(msg) => console.log(msg),
this.options,
);
}
build() {
async build() {
if (!this.packager) {
let { outputPath, packageCache } = await this.stage.ready();
if (!packageCache) {
// stages are allowed to share a package cache as an optimization, but
// they aren't required to. Whereas Packages are allowed to assume
// they will receive a packageCache instance.
packageCache = new PackageCache();
}
this.packager = new packagerClass(
outputPath,
this.outputPath,
(msg) => console.log(msg),
packageCache,
this.options,
);
}
return this.packager.build();

@@ -32,0 +40,0 @@ }

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc