Skip to content

Commit

Permalink
Merge pull request #10053 from ember-cli/hjblue/node-esm
Browse files Browse the repository at this point in the history
  • Loading branch information
rwjblue committed Oct 19, 2022
2 parents e79fe7d + fc771a4 commit f5a637d
Show file tree
Hide file tree
Showing 19 changed files with 325 additions and 174 deletions.
19 changes: 13 additions & 6 deletions lib/models/builder.js
Expand Up @@ -26,7 +26,6 @@ class Builder extends CoreObject {

// Use Broccoli 2.0 by default, if this fails due to .read/.rebuild API, fallback to broccoli-builder
this.broccoliBuilderFallback = false;
this.setupBroccoliBuilder();

this._instantiationStack = new Error().stack.replace(/[^\n]*\n/, '');
this._cleanup = this.cleanup.bind(this);
Expand All @@ -42,25 +41,31 @@ class Builder extends CoreObject {
* @method readBuildFile
* @param path The file path to read the build file from
*/
readBuildFile(path) {
async readBuildFile(path) {
// Load the build file
let buildFile = findBuildFile('ember-cli-build.js', path);
let buildFile = await findBuildFile(path);
if (buildFile) {
return buildFile({ project: this.project });
return await buildFile({ project: this.project });
}

throw new SilentError('No ember-cli-build.js found.');
}

async ensureBroccoliBuilder() {
if (this.builder === undefined) {
await this.setupBroccoliBuilder();
}
}

/**
* @private
* @method setupBroccoliBuilder
*/
setupBroccoliBuilder() {
async setupBroccoliBuilder() {
this.environment = this.environment || 'development';
process.env.EMBER_ENV = process.env.EMBER_ENV || this.environment;

this.tree = this.readBuildFile(this.project.root);
this.tree = await this.readBuildFile(this.project.root);

let broccoli = require('broccoli');

Expand Down Expand Up @@ -167,6 +172,8 @@ class Builder extends CoreObject {
* @return {Promise}
*/
async build(addWatchDirCallback, resultAnnotation) {
await this.ensureBroccoliBuilder();

let buildResults, uiProgressIntervalID;

try {
Expand Down
4 changes: 2 additions & 2 deletions lib/models/server-watcher.js
Expand Up @@ -3,8 +3,8 @@
const Watcher = require('./watcher');

module.exports = class ServerWatcher extends Watcher {
constructor(options) {
super(options);
constructor(options, build) {
super(options, build);

this.watcher.on('add', this.didAdd.bind(this));
this.watcher.on('delete', this.didDelete.bind(this));
Expand Down
29 changes: 23 additions & 6 deletions lib/models/watcher.js
Expand Up @@ -12,19 +12,35 @@ const eventTypeNormalization = {
change: 'changed',
};

const ConstructedFromBuilder = Symbol('Watcher.build');

module.exports = class Watcher extends CoreObject {
constructor(_options) {
constructor(_options, build) {
if (build !== ConstructedFromBuilder) {
throw new Error('instantiate Watcher with (await Watcher.build()).watcher, not new Watcher()');
}

super(_options);

this.verbose = true;
this.serving = _options.serving;
}

let options = this.buildOptions();
static async build(_options) {
let watcher = new this(_options, ConstructedFromBuilder);
await watcher.setupBroccoliWatcher(_options);

logger.info('initialize %o', options);
// This indirection is because Watcher instances are themselves spec
// noncompliant thennables (see the then() method) so returning watcher
// directly will interfere with `await Watcher.build()`
return { watcher };
}

this.serving = _options.serving;
async setupBroccoliWatcher() {
let options = this.buildOptions();

this.watcher = this.watcher || this.constructBroccoliWatcher(options);
logger.info('initialize %o', options);
this.watcher = this.watcher || (await this.constructBroccoliWatcher(options));

this.setupBroccoliChangeEvent();
this.watcher.on('buildStart', this._setupBroccoliWatcherBuild.bind(this));
Expand All @@ -38,8 +54,9 @@ module.exports = class Watcher extends CoreObject {
this.serveURL = serveURL;
}

constructBroccoliWatcher(options) {
async constructBroccoliWatcher(options) {
const { Watcher } = require('broccoli');
await this.builder.ensureBroccoliBuilder();
const { watchedSourceNodeWrappers } = this.builder.builder;

let watcher = new Watcher(this.builder, watchedSourceNodeWrappers, { saneOptions: options, ignored: this.ignored });
Expand Down
16 changes: 9 additions & 7 deletions lib/tasks/build-watch.js
Expand Up @@ -35,13 +35,15 @@ class BuildWatchTask extends Task {

let watcher =
options._watcher ||
new Watcher({
ui,
builder,
analytics: this.analytics,
options,
ignored: [path.resolve(this.project.root, options.outputPath)],
});
(
await Watcher.build({
ui,
builder,
analytics: this.analytics,
options,
ignored: [path.resolve(this.project.root, options.outputPath)],
})
).watcher;

await watcher;
// Run until failure or signal to exit
Expand Down
18 changes: 10 additions & 8 deletions lib/tasks/serve.js
Expand Up @@ -55,14 +55,16 @@ class ServeTask extends Task {

let watcher =
options._watcher ||
new Watcher({
ui: this.ui,
builder,
analytics: this.analytics,
options,
serving: true,
ignored: [path.resolve(this.project.root, options.outputPath)],
});
(
await Watcher.build({
ui: this.ui,
builder,
analytics: this.analytics,
options,
serving: true,
ignored: [path.resolve(this.project.root, options.outputPath)],
})
).watcher;

let serverRoot = './server';
let serverWatcher = null;
Expand Down
26 changes: 17 additions & 9 deletions lib/utilities/find-build-file.js
@@ -1,24 +1,32 @@
'use strict';
const findUp = require('find-up');
const path = require('path');
const url = require('url');

module.exports = function (file, dir) {
let buildFilePath = findUp.sync(file, { cwd: dir });
module.exports = async function (dir) {
let buildFilePath = null;

// Note: In the future this should throw
if (!buildFilePath) {
for (let ext of ['js', 'mjs', 'cjs']) {
let candidate = findUp.sync(`ember-cli-build.${ext}`, { cwd: dir });
if (candidate) {
buildFilePath = candidate;
break;
}
}

if (buildFilePath === null) {
return null;
}

process.chdir(path.dirname(buildFilePath));

let buildFile = null;
let buildFileUrl = url.pathToFileURL(buildFilePath);
try {
buildFile = require(buildFilePath);
// eslint-disable-next-line node/no-unsupported-features/es-syntax
let fn = (await import(buildFileUrl)).default;
return fn;
} catch (err) {
err.message = `Could not require '${file}': ${err.message}`;
err.message = `Could not \`import('${buildFileUrl}')\`: ${err.message}`;
throw err;
}

return buildFile;
};
28 changes: 28 additions & 0 deletions tests/acceptance/brocfile-smoke-test-slow.js
Expand Up @@ -61,6 +61,34 @@ describe('Acceptance: brocfile-smoke-test', function () {
await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'test');
});

it('builds with an ES modules ember-cli-build.js', async function () {
await fs.writeFile(
'ember-cli-build.js',
`
import EmberApp from 'ember-cli/lib/broccoli/ember-app.js';
export default async function (defaults) {
const app = new EmberApp(defaults, { });
return app.toTree();
};
`
);

let appPackageJson = await fs.readJson('package.json');
appPackageJson.type = 'module';
await fs.writeJson('package.json', appPackageJson);

// lib/utilities/find-build-file.js uses await import and so can handle ES module ember-cli-build.js
//
// However, broccoli-config-loader uses require, so files like
// config/environment.js must be in commonjs format. The way to mix ES and
// commonjs formats in node is with multiple `package.json`s
await fs.writeJson('config/package.json', { type: 'commonjs' });
console.log(process.cwd());
await runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'build');
});

it('without app/templates', async function () {
await copyFixtureFiles('brocfile-tests/pods-templates');
await fs.remove(path.join(process.cwd(), 'app/templates'));
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/build/node-esm/app/hello.txt
@@ -0,0 +1 @@
Hello world
3 changes: 3 additions & 0 deletions tests/fixtures/build/node-esm/app/intro.md
@@ -0,0 +1,3 @@
# Introduction

This is the introduction markdown file
3 changes: 3 additions & 0 deletions tests/fixtures/build/node-esm/app/outro.md
@@ -0,0 +1,3 @@
# Outro

This is the outro
1 change: 1 addition & 0 deletions tests/fixtures/build/node-esm/app/test.txt
@@ -0,0 +1 @@
This is a test
3 changes: 3 additions & 0 deletions tests/fixtures/build/node-esm/dist/intro.md
@@ -0,0 +1,3 @@
# Introduction

This is the introduction markdown file
3 changes: 3 additions & 0 deletions tests/fixtures/build/node-esm/dist/outro.md
@@ -0,0 +1,3 @@
# Outro

This is the outro
3 changes: 3 additions & 0 deletions tests/fixtures/build/node-esm/dist/text.txt
@@ -0,0 +1,3 @@
Hello world

This is a test
17 changes: 17 additions & 0 deletions tests/fixtures/build/node-esm/ember-cli-build.cjs
@@ -0,0 +1,17 @@
const mergeTrees = require('broccoli-merge-trees');
const concat = require('broccoli-concat');
const funnel = require('broccoli-funnel');

module.exports = function() {
const txt = funnel('app', {
include: ['*.txt'],
});
const md = funnel('app', {
include: ['*.md'],
});
const concated = concat(txt, {
outputFile: 'text.txt',
});

return mergeTrees([concated, md], {annotation: 'The final merge'});
}

0 comments on commit f5a637d

Please sign in to comment.