Skip to content

Commit

Permalink
Refactor to use source-map-explorer
Browse files Browse the repository at this point in the history
Closes #221
  • Loading branch information
simonihmig committed Jun 18, 2019
1 parent d066c35 commit c3d94d7
Show file tree
Hide file tree
Showing 6 changed files with 1,625 additions and 404 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
@@ -1,7 +1,7 @@
module.exports = {
root: true,
parserOptions: {
ecmaVersion: 2015,
ecmaVersion: 2017,
},
plugins: [
'node'
Expand Down
182 changes: 43 additions & 139 deletions index.js
Expand Up @@ -2,19 +2,12 @@

const path = require('path');
const debug = require('debug')('ember-cli-bundle-analyzer');
const { createOutput, summarizeAll } = require('broccoli-concat-analyser');
const fs = require('fs');
const sane = require('sane');
const touch = require('touch');
const hashFiles = require('hash-files').sync;
const tmp = require('tmp');
const VersionChecker = require('ember-cli-version-checker');
const interceptStdout = require('intercept-stdout');
const injectLivereload = require('./lib/inject-livereload');
const explore = require('source-map-explorer').explore;
const glob = require('fast-glob');

const REQUEST_PATH = '/_analyze';
const BROCCOLI_CONCAT_PATH_SUPPORT = '3.6.0';
const BROCCOLI_CONCAT_LAZY_SUPPORT = '3.7.0';

module.exports = {
name: require('./package').name,
Expand All @@ -25,19 +18,11 @@ module.exports = {
_buildCallback: null,
_computePromise: null,
_buildPromise: null,
bundleFiles: ['dist/assets/*.js', 'dist/assets/*.css'],

init() {
this._super.init && this._super.init.apply(this, arguments);
debug(`${this.name} started.`);

let checker = new VersionChecker(this);
this.concatVersion = checker.for('broccoli-concat');

if (this.concatVersion.lt(BROCCOLI_CONCAT_LAZY_SUPPORT)) {
debug(`broccoli-concat v${this.concatVersion.version} does not support lazy stats activation, forced to activate prematurely.`);
this.enableStats();
}
this.initConcatStatsPath();
},

included: function(app) {
Expand All @@ -51,22 +36,12 @@ module.exports = {
}

if (options.ignoreTestFiles !== false) {
ignoredFiles = ignoredFiles.concat('tests.js', 'test-support.js', 'test-support.css', '*-test.js');
ignoredFiles = ignoredFiles.concat('tests.js', 'test-support.js', 'test-support.css');
}

this.ignoredFiles = ignoredFiles;
},

initConcatStatsPath() {
// if broccoli-concat supports a custom path for stats data, put the data in a temp folder outside of the project!
if (this.concatVersion.gte(BROCCOLI_CONCAT_PATH_SUPPORT)) {
this.concatStatsPath = tmp.dirSync().name;
process.env.CONCAT_STATS_PATH = this.concatStatsPath;
} else {
this.concatStatsPath = path.join(process.cwd(), 'concat-stats-for');
}
},

serverMiddleware(config) {
if (this.isEnabled()) {
this.addAnalyzeMiddleware(config);
Expand All @@ -76,74 +51,62 @@ module.exports = {
addAnalyzeMiddleware(config) {
let app = config.app;

app.get(REQUEST_PATH, (req, res) => {
app.get(REQUEST_PATH, async (req, res) => {
this.debugRequest(req);
this.initBuildWatcher();
Promise.resolve()
.then(() => this._buildPromise)
.then(() => {
if (!this.hasStats()) {
res.sendFile(path.join(__dirname, 'lib', 'output', 'computing', 'index.html'));
return;
}

if (!this._statsOutput) {
res.sendFile(path.join(__dirname, 'lib', 'output', 'computing', 'index.html'));
} else {
res.send(this._statsOutput);
}
});
await this._buildPromise;
if (!this._statsOutput) {
res.sendFile(path.join(__dirname, 'lib', 'output', 'computing', 'index.html'));
} else {
res.send(this._statsOutput);
}
});

app.get(`${REQUEST_PATH}/compute`, (req, res) => {
this.initWatcher();
app.get(`${REQUEST_PATH}/compute`, async (req, res) => {
this.debugRequest(req);
this.initBuildWatcher();
Promise.resolve()
.then(() => this._buildPromise)
.then(() => {
if (!this.hasStats()) {
this.enableStats();
this.triggerBuild();
return this._initialBuildPromise;
}
})
.then(() => {
// @todo make this throw an exception when there are no stats
this.computeOutput()
.then((output) => {
this._statsOutput = injectLivereload(output);
res.redirect(REQUEST_PATH);
})
.catch((e) => {
this.ui.writeError(e);
res.sendFile(path.join(__dirname, 'lib', 'output', 'no-stats', 'index.html'));
});
})
.catch(e => {
await this._buildPromise;
try {
let output = await this.computeOutput()
this._statsOutput = injectLivereload(output);
res.redirect(REQUEST_PATH);
}
catch(e) {
if (e.errors) {
e.errors.map(e => this.ui.writeError(e.error));
} else {
this.ui.writeError(e);
});
}
res.sendFile(path.join(__dirname, 'lib', 'output', 'no-stats', 'index.html'));
}
});
},

computeOutput() {
debugRequest(req) {
debug(`${req.method} ${req.url}`);
},

async computeOutput() {
if (!this._computePromise) {
debug('Computing stats...');
this._computePromise = summarizeAll(this.concatStatsPath, this.ignoredFiles)
.then(() => {
debug('Computing finished.');

let files = await glob(this.bundleFiles, { ignore: this.ignoredFiles.map(file => `dist/assets/${file}`) });
debug('Found these bundles: ' + files.join(', '));
this._computePromise = explore(files, { output: { format: 'html' } })
.then((result) => {
debug('Computing finished: ' + JSON.stringify(result));
this._computePromise = null;
return createOutput(this.concatStatsPath);
return result.output;
});
}
return this._computePromise;
},

initBuildWatcher() {
let resolve;
let initialResolve;
if (this._buildWatcher) {
return;
}
this._initialBuildPromise = new Promise((_resolve) => initialResolve = _resolve);
this._buildWatcher = interceptStdout((text) => {
if (text instanceof Buffer) {
text = text.toString();
Expand All @@ -155,81 +118,22 @@ module.exports = {
if (text.match(/file (added|changed|deleted)/)) {
debug('Rebuild detected');
this._buildPromise = new Promise((_resolve) => resolve = _resolve);
this._statsOutput = null;
}

if (text.match(/Build successful/)) {
if (!resolve) {
return;
}
debug('Finished build detected');
setTimeout(() => {
resolve();
initialResolve();
}, 1000);
}
});
},

initWatcher() {
if (this._hasWatcher) {
return;
}
debug('Initializing watcher on json files');
let watcher = sane(this.concatStatsPath, { glob: ['*.json'], ignored: ['*.out.json'] });
watcher.on('change', this._handleWatcher.bind(this));
watcher.on('add', this._handleWatcher.bind(this));
watcher.on('delete', this._handleWatcher.bind(this));
this._hasWatcher = true;
},

_handleWatcher(filename, root/*, stat*/) {
let file = path.join(root, filename);
let hash = hashFiles({ files: [file] });

if (this._hashedFiles[filename] !== hash) {
debug(`Cache invalidated by ${filename}`);
this._statsOutput = null;
this._hashedFiles[filename] = hash;
}
},

isEnabled() {
return true;
},

hasStats() {
return !!process.env.CONCAT_STATS && this.concatStatsPath && fs.existsSync(this.concatStatsPath);
},

enableStats() {
debug('Enabled stats generation');
process.env.CONCAT_STATS = 'true';
},

triggerBuild() {
debug('Triggering build');
let mainFile = this.getMainFile();
if (mainFile) {
debug(`Touching ${mainFile}`);
touch(mainFile);
} else {
throw new Error('No main file found to trigger build');
}
},

getMainFile() {
let { root } = this.project;
let mainCandidates = [
'app/app.js', // app
'src/main.js', // MU
'tests/dummy/app/app.js', // addon dummy app
'app/app.ts', // app (TS)
'src/main.ts', // MU (TS)
'tests/dummy/app/app.ts' // addon dummy app (TS)
]
.map((item) => path.join(root, item));

for (let mainFile of mainCandidates) {
if (fs.existsSync(mainFile)) {
return mainFile
}
}
}
};
24 changes: 3 additions & 21 deletions node-tests/acceptance-test.js
@@ -1,8 +1,6 @@
const chai = require('chai');
const chaiHttp = require('chai-http');
const { App } = require('ember-cli-addon-tests');
const tmp = require('tmp');

const { expect } = chai;
const port = 4444;
const baseUrl = `http://localhost:${port}`;
Expand Down Expand Up @@ -49,27 +47,11 @@ describe('acceptance', function() {
.and.be.html;

expect(res.text)
.to.include('<script>var SUMMARY = {')
.and.to.include('"label": "assets/dummy.js');
.to.include('var treeDataMap = {')
.and.to.include('assets/vendor.js')
.and.to.include('assets/dummy.js');
});
});
});

it('_analyze/compute shows error page', function() {
let origPath = process.env.CONCAT_STATS_PATH;
// setting a different stats path will cause a rejected promise during computation
process.env.CONCAT_STATS_PATH = tmp.dirSync().name;
return chai.request(baseUrl)
.get('/_analyze/compute')
.then((res) => {
expect(res).to.have.status(200)
.and.be.html;
expect(res.text).to.include('Dang! Looks like no stats are available.');
})
.then(
() => process.env.CONCAT_STATS_PATH = origPath,
() => process.env.CONCAT_STATS_PATH = origPath
);
});

});
12 changes: 5 additions & 7 deletions package.json
Expand Up @@ -25,23 +25,20 @@
"test": "mocha node-tests/**/*-test.js --recursive"
},
"dependencies": {
"broccoli-concat-analyser": "^4.3.4",
"debug": "^3.1.0",
"ember-cli-babel": "^7.7.3",
"ember-cli-version-checker": "^2.1.2",
"hash-files": "^1.1.1",
"fast-glob": "^2.2.7",
"intercept-stdout": "^0.1.2",
"node-html-light": "^1.0.0",
"sane": "^3.0.0",
"tmp": "^0.0.33",
"touch": "^3.1.0"
"source-map-explorer": "^2.0.0"
},
"devDependencies": {
"@ember/optional-features": "^0.7.0",
"chai": "^4.1.2",
"chai-http": "^4.2.0",
"ember-auto-import": "^1.4.0",
"ember-cli": "~3.10.1",
"ember-cli-addon-tests": "^0.11.0",
"ember-cli-babel": "^7.7.3",
"ember-cli-dependency-checker": "^3.1.0",
"ember-cli-htmlbars": "^3.0.1",
"ember-cli-htmlbars-inline-precompile": "^2.1.0",
Expand All @@ -60,6 +57,7 @@
"eslint-plugin-mocha": "^5.2.0",
"eslint-plugin-node": "^9.0.1",
"loader.js": "^4.7.0",
"lodash-es": "^4.17.11",
"mocha": "^5.2.0"
},
"engines": {
Expand Down
4 changes: 4 additions & 0 deletions tests/dummy/app/styles/app.css
@@ -0,0 +1,4 @@
body {
font-family: 'Helvetica Neue', 'Arial', sans-serif;
color: #111;
}

0 comments on commit c3d94d7

Please sign in to comment.