88 | To run your app with Electron, execute the following command under your
89 | Console (or Terminal):
90 |
91 |
92 |
93 |
94 |
95 | The path-to-your-app should be the path to your own Electron
96 | app, you can read the
97 |
102 | guide in Electron's
103 |
108 | on how to write one.
109 |
110 |
111 |
112 | Or you can just drag your app here to run it:
113 |
114 |
115 |
116 | Drag your app here to run it
117 |
118 |
119 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/test/fixtures/inline-valid-2.html:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
23 |
24 |
70 |
71 | {{text}}
72 |
73 |
74 |
75 |
165 |
--------------------------------------------------------------------------------
/test/fixtures/inline-valid-3.html:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
23 |
24 |
32 |
33 |
79 |
80 | {{text}}
81 |
82 |
83 |
84 |
174 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## electron-compile
2 |
3 |  
4 |
5 | electron-compile compiles JS and CSS on the fly with a single call in your app's 'ready' function.
6 |
7 | For JavaScript:
8 |
9 | * JavaScript ES6/ES7 (via Babel)
10 | * TypeScript
11 | * CoffeeScript
12 |
13 | For CSS:
14 |
15 | * LESS
16 |
17 | For HTML:
18 |
19 | * Jade
20 |
21 | ### How does it work? (Easiest Way)
22 |
23 | Change your reference to `electron-prebuilt` to `electron-prebuilt-compile`. Tada! You did it.
24 |
25 | ### Wait, seriously?
26 |
27 | Yeah. `electron-prebuilt-compile` is like an `electron-prebuilt` that Just Works with all of these languages above.
28 |
29 | ### How does it work? (Slightly Harder Way)
30 |
31 | First, add `electron-compile` and `electron-compilers` as a `devDependency`.
32 |
33 | ```sh
34 | npm install --save electron-compile
35 | npm install --save-dev electron-compilers
36 | ```
37 |
38 | Create a new file that will be the entry point of your app (perhaps changing 'main' in package.json) - you need to pass in the root directory of your application, which will vary based on your setup. The root directory is the directory that your `package.json` is in.
39 |
40 | ```js
41 | // Assuming this file is ./src/es6-init.js
42 | var appRoot = path.join(__dirname, '..');
43 |
44 | // ...and that your main app is called ./src/main.js. This is written as if
45 | // you were going to `require` the file from here.
46 | require('electron-compile').init(appRoot, './main');
47 | ```
48 |
49 |
50 | ### I did it, now what?
51 |
52 | From then on, you can now simply include files directly in your HTML, no need for cross-compilation:
53 |
54 | ```html
55 |
56 |
57 |
58 |
59 | ```
60 |
61 | or just require them in:
62 |
63 | ```js
64 | require('./mylib') // mylib.ts
65 | ```
66 |
67 | ### Something isn't working / I'm getting weird errors
68 |
69 | electron-compile uses the [debug module](https://github.com/visionmedia/debug), set the DEBUG environment variable to debug what electron-compile is doing:
70 |
71 | ```sh
72 | ## Debug just electron-compile
73 | DEBUG=electron-compile:* npm start
74 |
75 | ## Grab everything except for Babel which is very noisy
76 | DEBUG=*,-babel npm start
77 | ```
78 |
79 | ### How do I set up (Babel / LESS / whatever) the way I want?
80 |
81 | If you've got a `.babelrc` and that's all you want to customize, you can simply use it directly. electron-compile will respect it, even the environment-specific settings. If you want to customize other compilers, use a `.compilerc` file. Here's an example:
82 |
83 | ```js
84 | {
85 | "application/javascript": {
86 | "presets": ["stage-0", "es2015", "react"],
87 | "sourceMaps": "inline"
88 | },
89 | "text/less": {
90 | "dumpLineNumbers": "comments"
91 | }
92 | }
93 | ```
94 |
95 | `.compilerc` also accepts environments with the same syntax as `.babelrc`:
96 |
97 | ```js
98 | {
99 | "env": {
100 | "development": {
101 | "application/javascript": {
102 | "presets": ["stage-0", "es2015", "react"],
103 | "sourceMaps": "inline"
104 | },
105 | "text/less": {
106 | "dumpLineNumbers": "comments"
107 | }
108 | },
109 | "production": {
110 | "application/javascript": {
111 | "presets": ["stage-0", "es2015", "react"]
112 | "sourceMaps": "none"
113 | }
114 | }
115 | }
116 | }
117 | ```
118 |
119 | The opening Object is a list of MIME Types, and options passed to the compiler implementation. These parameters are documented here:
120 |
121 | * Babel - http://babeljs.io/docs/usage/options
122 | * CoffeeScript - http://coffeescript.org/documentation/docs/coffee-script.html#section-5
123 | * TypeScript - https://github.com/Microsoft/TypeScript/blob/v1.5.0-beta/bin/typescriptServices.d.ts#L1076
124 | * LESS - http://lesscss.org/usage/index.html#command-line-usage-options
125 | * Jade - http://jade-lang.com/api
126 |
127 | ## How can I precompile my code for release-time?
128 |
129 | electron-compile comes with a command-line application to pre-create a cache for you.
130 |
131 | ```sh
132 | Usage: electron-compile --appDir [root-app-dir] paths...
133 |
134 | Options:
135 | -a, --appdir The top-level application directory (i.e. where your
136 | package.json is)
137 | -v, --verbose Print verbose information
138 | -h, --help Show help
139 | ```
140 |
141 | Run `electron-compile` on all of your application assets, even if they aren't strictly code (i.e. your static assets like PNGs). electron-compile will recursively walk the given directories.
142 |
143 | ```sh
144 | electron-compile --appDir /path/to/my/app ./src ./static
145 | ```
146 |
147 | ### But I use Grunt / Gulp / I want to do Something Interesting
148 |
149 | Compilation also has its own API, check out the [documentation](http://electronjs.github.io/electron-compile/docs/badge.svg) for more information.
150 |
--------------------------------------------------------------------------------
/test/inline-html-compiler.js:
--------------------------------------------------------------------------------
1 | import './support.js';
2 |
3 | import fs from 'fs';
4 | import path from 'path';
5 | import cheerio from 'cheerio';
6 | import pify from 'pify';
7 | import _ from 'lodash';
8 |
9 | const validInputs = [
10 | 'inline-valid.html',
11 | 'inline-valid-2.html',
12 | 'inline-valid-3.html'
13 | ];
14 |
15 | const pfs = pify(fs);
16 | const InlineHtmlCompiler = global.compilersByMimeType['text/html'];
17 |
18 | const d = require('debug')('test:inline-html-compiler');
19 |
20 | describe('The inline HTML compiler', function() {
21 | beforeEach(function() {
22 | let compilers = _.reduce(Object.keys(global.compilersByMimeType), (acc, x) => {
23 | let Klass = global.compilersByMimeType[x];
24 | acc[x] = new Klass();
25 |
26 | return acc;
27 | }, {});
28 |
29 | compilers['application/javascript'].compilerOptions = {
30 | "presets": ["stage-0", "es2015", "react"],
31 | "sourceMaps": "inline"
32 | };
33 |
34 | compilers['text/coffeescript'].compilerOptions = { sourceMap: true };
35 |
36 | this.fixture = InlineHtmlCompiler.createFromCompilers(compilers);
37 | });
38 |
39 | _.each(validInputs, (inputFile) => {
40 | it('should compile the valid fixture ' + inputFile, async function() {
41 | let input = path.join(__dirname, '..', 'test', 'fixtures', inputFile);
42 |
43 | let cc = {};
44 | expect(await this.fixture.shouldCompileFile(input, cc)).to.be.ok;
45 |
46 | let code = await pfs.readFile(input, 'utf8');
47 | let df = await this.fixture.determineDependentFiles(input, code, cc);
48 |
49 | expect(df.length).to.equal(0);
50 |
51 | let result = await this.fixture.compile(code, input, cc);
52 | expect(result.mimeType).to.equal('text/html');
53 |
54 | let $ = cheerio.load(result.code);
55 | let tags = $('script');
56 | expect(tags.length > 0).to.be.ok;
57 |
58 | $('script').map((__, el) => {
59 | let text = $(el).text();
60 | if (!text || text.length < 2) return;
61 |
62 | if ($(el).attr('type').match(/handlebars/)) return;
63 |
64 | expect(_.find(text.split('\n'), (l) => l.match(/sourceMappingURL/))).to.be.ok;
65 | });
66 | });
67 |
68 | it('should compile the valid fixture ' + inputFile + ' synchronously', function() {
69 | let input = path.join(__dirname, '..', 'test', 'fixtures', inputFile);
70 |
71 | let cc = {};
72 | expect(this.fixture.shouldCompileFileSync(input, cc)).to.be.ok;
73 |
74 | let code = fs.readFileSync(input, 'utf8');
75 | let df = this.fixture.determineDependentFilesSync(input, code, cc);
76 |
77 | expect(df.length).to.equal(0);
78 |
79 | let result = this.fixture.compileSync(code, input, cc);
80 | expect(result.mimeType).to.equal('text/html');
81 |
82 | let $ = cheerio.load(result.code);
83 | let tags = $('script');
84 | expect(tags.length > 0).to.be.ok;
85 |
86 | $('script').map((__, el) => {
87 | let text = $(el).text();
88 | if (!text || text.length < 2) return;
89 |
90 | d($(el).attr('type'));
91 | if ($(el).attr('type').match(/handlebars/)) return;
92 |
93 | d(text);
94 | expect(_.find(text.split('\n'), (l) => l.match(/sourceMappingURL/))).to.be.ok;
95 | });
96 | });
97 | });
98 |
99 | it('should remove protocol-relative URLs because they are dumb', async function() {
100 | let input = path.join(__dirname, '..', 'test', 'fixtures', 'roboto.html');
101 |
102 | let cc = {};
103 | expect(await this.fixture.shouldCompileFile(input, cc)).to.be.ok;
104 |
105 | let code = await pfs.readFile(input, 'utf8');
106 | let df = await this.fixture.determineDependentFiles(input, code, cc);
107 |
108 | expect(df.length).to.equal(0);
109 |
110 | let result = await this.fixture.compile(code, input, cc);
111 |
112 | expect(result.code.length > 0).to.be.ok;
113 | expect(result.mimeType).to.equal('text/html');
114 |
115 | let $ = cheerio.load(result.code);
116 | let tags = $('link');
117 | expect(tags.length === 1).to.be.ok;
118 | expect($(tags[0]).attr('href').match(/^https/i)).to.be.ok;
119 | });
120 |
121 | it('should canonicalize x-require paths', async function() {
122 | let input = path.join(__dirname, '..', 'test', 'fixtures', 'x-require-valid.html');
123 |
124 | let cc = {};
125 | expect(await this.fixture.shouldCompileFile(input, cc)).to.be.ok;
126 |
127 | let code = await pfs.readFile(input, 'utf8');
128 | let df = await this.fixture.determineDependentFiles(input, code, cc);
129 |
130 | expect(df.length).to.equal(0);
131 |
132 | let result = await this.fixture.compile(code, input, cc);
133 |
134 | expect(result.code.length > 0).to.be.ok;
135 | expect(result.mimeType).to.equal('text/html');
136 |
137 | let $ = cheerio.load(result.code);
138 | let tags = $('x-require');
139 | expect(tags.length === 1).to.be.ok;
140 |
141 | $('x-require').map((__, el) => {
142 | let src = $(el).attr('src');
143 | expect(_.find(src.split(/[\\\/]/), (x) => x === '.' || x === '..')).not.to.be.ok;
144 | });
145 | });
146 | });
147 |
--------------------------------------------------------------------------------
/test/compile-cache.js:
--------------------------------------------------------------------------------
1 | import './support.js';
2 |
3 | import fs from 'fs';
4 | import path from 'path';
5 | import rimraf from 'rimraf';
6 | import mkdirp from 'mkdirp';
7 | import FileChangeCache from '../src/file-change-cache';
8 | import CompileCache from '../src/compile-cache';
9 | import pify from 'pify';
10 |
11 | const pfs = pify(fs);
12 |
13 | let testCount=0;
14 |
15 | describe('The compile cache', function() {
16 | beforeEach(function() {
17 | this.appRootDir = path.join(__dirname, '..');
18 | this.fileChangeCache = new FileChangeCache(this.appRootDir);
19 |
20 | this.tempCacheDir = path.join(__dirname, `__compile_cache_${testCount++}`);
21 | mkdirp.sync(this.tempCacheDir);
22 | this.fixture = new CompileCache(this.tempCacheDir, this.fileChangeCache);
23 | });
24 |
25 | afterEach(function() {
26 | rimraf.sync(this.tempCacheDir);
27 | });
28 |
29 | it('Should only call compile once for the same file', async function() {
30 | let inputFile = path.resolve(__dirname, '..', 'src', 'compile-cache.js');
31 | let callCount = 0;
32 |
33 | let fetcher = async function(filePath, hashInfo) {
34 | callCount++;
35 |
36 | let code = hashInfo.sourceCode || await pfs.readFile(filePath, 'utf8');
37 | let mimeType = 'application/javascript';
38 | return { code, mimeType };
39 | };
40 |
41 | let result = await this.fixture.getOrFetch(inputFile, fetcher);
42 |
43 | expect(result.mimeType).to.equal('application/javascript');
44 | expect(result.code.length > 10).to.be.ok;
45 | expect(callCount).to.equal(1);
46 |
47 | result = await this.fixture.getOrFetch(inputFile, fetcher);
48 |
49 | expect(result.mimeType).to.equal('application/javascript');
50 | expect(result.code.length > 10).to.be.ok;
51 | expect(callCount).to.equal(1);
52 |
53 | this.fixture = new CompileCache(this.tempCacheDir, this.fileChangeCache);
54 |
55 | result = await this.fixture.getOrFetch(inputFile, fetcher);
56 |
57 | expect(result.mimeType).to.equal('application/javascript');
58 | expect(result.code.length > 10).to.be.ok;
59 | expect(callCount).to.equal(1);
60 | });
61 |
62 | it('Should roundtrip binary files', async function() {
63 | let inputFile = path.resolve(__dirname, '..', 'test', 'fixtures', 'binaryfile.zip');
64 | let hashInfo = await this.fileChangeCache.getHashForPath(inputFile);
65 |
66 | await this.fixture.save(hashInfo, hashInfo.binaryData, 'application/zip');
67 |
68 | let fetcher = async function() {
69 | throw new Error("No");
70 | };
71 |
72 | let result = await this.fixture.getOrFetch(inputFile, fetcher);
73 | expect(result.mimeType).to.equal('application/zip');
74 | expect(result.binaryData.length).to.equal(hashInfo.binaryData.length);
75 |
76 | this.fixture = new CompileCache(this.tempCacheDir, this.fileChangeCache);
77 |
78 | result = await this.fixture.getOrFetch(inputFile, fetcher);
79 | expect(result.mimeType).to.equal('application/zip');
80 | expect(result.binaryData.length).to.equal(hashInfo.binaryData.length);
81 | });
82 |
83 | it('Should roundtrip binary files synchronously', function() {
84 | let inputFile = path.resolve(__dirname, '..', 'test', 'fixtures', 'binaryfile.zip');
85 | let hashInfo = this.fileChangeCache.getHashForPathSync(inputFile);
86 |
87 | this.fixture.saveSync(hashInfo, hashInfo.binaryData, 'application/zip');
88 |
89 | let fetcher = function() {
90 | throw new Error("No");
91 | };
92 |
93 | let result = this.fixture.getOrFetchSync(inputFile, fetcher);
94 | expect(result.mimeType).to.equal('application/zip');
95 | expect(result.binaryData.length).to.equal(hashInfo.binaryData.length);
96 |
97 | this.fixture = new CompileCache(this.tempCacheDir, this.fileChangeCache);
98 |
99 | result = this.fixture.getOrFetchSync(inputFile, fetcher);
100 | expect(result.mimeType).to.equal('application/zip');
101 | expect(result.binaryData.length).to.equal(hashInfo.binaryData.length);
102 | });
103 |
104 | it('Should only call compile once for the same file synchronously', function() {
105 | let inputFile = path.resolve(__dirname, '..', 'src', 'compile-cache.js');
106 | let callCount = 0;
107 |
108 | let fetcher = function(filePath, hashInfo) {
109 | callCount++;
110 |
111 | let code = hashInfo.sourceCode || fs.readFileSync(filePath, 'utf8');
112 | let mimeType = 'application/javascript';
113 |
114 | return { code, mimeType };
115 | };
116 |
117 | let result = this.fixture.getOrFetchSync(inputFile, fetcher);
118 |
119 | expect(result.mimeType).to.equal('application/javascript');
120 | expect(result.code.length > 10).to.be.ok;
121 | expect(callCount).to.equal(1);
122 |
123 | result = this.fixture.getOrFetchSync(inputFile, fetcher);
124 |
125 | expect(result.mimeType).to.equal('application/javascript');
126 | expect(result.code.length > 10).to.be.ok;
127 | expect(callCount).to.equal(1);
128 |
129 | this.fixture = new CompileCache(this.tempCacheDir, this.fileChangeCache);
130 |
131 | result = this.fixture.getOrFetchSync(inputFile, fetcher);
132 |
133 | expect(result.mimeType).to.equal('application/javascript');
134 | expect(result.code.length > 10).to.be.ok;
135 | expect(callCount).to.equal(1);
136 | });
137 |
138 | it('Shouldnt cache compile failures', async function() {
139 | let inputFile = path.resolve(__dirname, '..', 'lib', 'compile-cache.js');
140 | let callCount = 0;
141 | let weBlewUpCount = 0;
142 |
143 | let fetcher = async function() {
144 | callCount++;
145 | throw new Error("Lolz");
146 | };
147 |
148 | try {
149 | await this.fixture.getOrFetch(inputFile, fetcher);
150 | } catch (e) {
151 | weBlewUpCount++;
152 | }
153 |
154 | expect(callCount).to.equal(1);
155 | expect(weBlewUpCount).to.equal(1);
156 |
157 | try {
158 | await this.fixture.getOrFetch(inputFile, fetcher);
159 | } catch (e) {
160 | weBlewUpCount++;
161 | }
162 |
163 | expect(callCount).to.equal(2);
164 | expect(weBlewUpCount).to.equal(2);
165 | });
166 | });
167 |
--------------------------------------------------------------------------------
/test/fixtures/valid.js:
--------------------------------------------------------------------------------
1 | 'use babel';
2 |
3 | import crypto from 'crypto';
4 | import path from 'path';
5 | import fs from 'fs';
6 | import mkdirp from 'mkdirp';
7 | import _ from 'lodash';
8 |
9 | export default class CompileCache {
10 | constructor() {
11 | this.stats = {
12 | hits: 0,
13 | misses: 0
14 | };
15 |
16 | this.cacheDir = null;
17 | this.jsCacheDir = null;
18 | this.seenFilePaths = {};
19 | }
20 |
21 | getCompilerInformation() {
22 | throw new Error("Implement this in a derived class");
23 | }
24 |
25 | compile(sourceCode, filePath, cachePath) {
26 | throw new Error("Implement this in a derived class");
27 | }
28 |
29 | getMimeType() {
30 | throw new Error("Implement this in a derived class");
31 | }
32 |
33 | initializeCompiler() {
34 | throw new Error("Implement this in a derived class");
35 | }
36 |
37 | shouldCompileFile(sourceCode, fullPath) {
38 | this.ensureInitialized();
39 | let lowerPath = fullPath.toLowerCase();
40 |
41 | // NB: require() normally does this for us, but in our protocol hook we
42 | // need to do this ourselves
43 | return _.some(
44 | this.extensions,
45 | (ext) => lowerPath.lastIndexOf(ext) + ext.length === lowerPath.length);
46 | }
47 |
48 | ///
49 | /// shasum - Hash with an update() method.
50 | /// value - Must be a value that could be returned by JSON.parse().
51 | ///
52 | updateDigestForJsonValue(shasum, value) {
53 | // Implmentation is similar to that of pretty-printing a JSON object, except:
54 | // * Strings are not escaped.
55 | // * No effort is made to avoid trailing commas.
56 | // These shortcuts should not affect the correctness of this function.
57 | const type = typeof(value);
58 |
59 | if (type === 'string') {
60 | shasum.update('"', 'utf8');
61 | shasum.update(value, 'utf8');
62 | shasum.update('"', 'utf8');
63 | return;
64 | }
65 |
66 | if (type === 'boolean' || type === 'number') {
67 | shasum.update(value.toString(), 'utf8');
68 | return;
69 | }
70 |
71 | if (value === null) {
72 | shasum.update('null', 'utf8');
73 | return;
74 | }
75 |
76 | if (Array.isArray(value)) {
77 | shasum.update('[', 'utf8');
78 | for (let i=0; i < value.length; i++) {
79 | this.updateDigestForJsonValue(shasum, value[i]);
80 | shasum.update(',', 'utf8');
81 | }
82 | shasum.update(']', 'utf8');
83 | return;
84 | }
85 |
86 | // value must be an object: be sure to sort the keys.
87 | let keys = Object.keys(value);
88 | keys.sort();
89 |
90 | shasum.update('{', 'utf8');
91 |
92 | for (let i=0; i < keys.length; i++) {
93 | this.updateDigestForJsonValue(shasum, keys[i]);
94 | shasum.update(': ', 'utf8');
95 | this.updateDigestForJsonValue(shasum, value[keys[i]]);
96 | shasum.update(',', 'utf8');
97 | }
98 |
99 | shasum.update('}', 'utf8');
100 | }
101 |
102 | createDigestForCompilerInformation() {
103 | let sha1 = crypto.createHash('sha1');
104 | this.updateDigestForJsonValue(sha1, this.getCompilerInformation());
105 | return sha1.digest('hex');
106 | }
107 |
108 | getCachePath(sourceCode) {
109 | let digest = crypto.createHash('sha1').update(sourceCode, 'utf8').digest('hex');
110 |
111 | if (!this.jsCacheDir) {
112 | this.jsCacheDir = path.join(this.cacheDir, this.createDigestForCompilerInformation());
113 | mkdirp.sync(this.jsCacheDir);
114 | }
115 |
116 | return path.join(this.jsCacheDir, `${digest}.js`);
117 | }
118 |
119 | getCachedJavaScript(cachePath) {
120 | try {
121 | let ret = fs.readFileSync(cachePath, 'utf8');
122 | this.stats.hits++;
123 |
124 | return ret;
125 | } catch (e) {
126 | return null;
127 | }
128 | }
129 |
130 | saveCachedJavaScript(cachePath, js) {
131 | fs.writeFileSync(cachePath, js);
132 | }
133 |
134 | // Function that obeys the contract of an entry in the require.extensions map.
135 | // Returns the transpiled version of the JavaScript code at filePath, which is
136 | // either generated on the fly or pulled from cache.
137 | loadFile(module, filePath, returnOnly=false, sourceCode=null) {
138 | this.ensureInitialized();
139 |
140 | let fullPath = path.resolve(filePath);
141 | this.seenFilePaths[path.dirname(filePath)] = true;
142 |
143 | sourceCode = sourceCode || fs.readFileSync(filePath, 'utf8');
144 |
145 | if (!this.shouldCompileFile(sourceCode, fullPath)) {
146 | if (returnOnly) return sourceCode;
147 | return module._compile(sourceCode, filePath);
148 | }
149 |
150 | // NB: We do all of these backflips in order to not load compilers unless
151 | // we actually end up using them, since loading them is typically fairly
152 | // expensive
153 | if (!this.compilerInformation.version) {
154 | this.compilerInformation.version = this.initializeCompiler();
155 | }
156 |
157 | let cachePath = this.getCachePath(sourceCode);
158 | let js = this.getCachedJavaScript(cachePath);
159 |
160 | if (!js) {
161 | js = this.compile(sourceCode, filePath, cachePath);
162 | this.stats.misses++;
163 |
164 | this.saveCachedJavaScript(cachePath, js);
165 | }
166 |
167 | if (returnOnly) return js;
168 | return module._compile(js, filePath);
169 | }
170 |
171 | register() {
172 | this.ensureInitialized();
173 |
174 | for (let i=0; i < this.extensions.length; i++) {
175 | Object.defineProperty(require.extensions, `.${this.extensions[i]}`, {
176 | enumerable: true,
177 | writable: false,
178 | value: (module, filePath) => this.loadFile(module, filePath)
179 | });
180 | }
181 | }
182 |
183 | ensureInitialized() {
184 | if (this.extensions) return;
185 |
186 | let info = this.getCompilerInformation();
187 |
188 | if (!info.extension && !info.extensions) {
189 | throw new Error("Compiler must register at least one extension in getCompilerInformation");
190 | }
191 |
192 | this.extensions = (info.extensions ? info.extensions : [info.extension]);
193 | }
194 |
195 | setCacheDirectory(newCacheDir) {
196 | if (this.cacheDir === newCacheDir) return;
197 |
198 | this.cacheDir = newCacheDir;
199 | this.jsCacheDir = null;
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/src/protocol-hook.js:
--------------------------------------------------------------------------------
1 | import './babel-maybefill';
2 | import url from 'url';
3 | import fs from 'fs';
4 | import mime from 'mime-types';
5 |
6 | import CompilerHost from './compiler-host';
7 |
8 | const magicWords = "__magic__file__to__help__electron__compile.js";
9 | const magicGlobalForRootCacheDir = '__electron_compile_root_cache_dir';
10 | const magicGlobalForAppRootDir = '__electron_compile_app_root_dir';
11 |
12 | const d = require('debug')('electron-compile:protocol-hook');
13 |
14 | let protocol = null;
15 |
16 | /**
17 | * Adds our script header to the top of all HTML files
18 | *
19 | * @private
20 | */
21 | export function rigHtmlDocumentToInitializeElectronCompile(doc) {
22 | let lines = doc.split("\n");
23 | let replacement = ``;
24 | let replacedHead = false;
25 |
26 | for (let i=0; i < lines.length; i++) {
27 | if (!lines[i].match(//i)) continue;
28 |
29 | lines[i] = (lines[i]).replace(//i, replacement);
30 | replacedHead = true;
31 | break;
32 | }
33 |
34 | if (!replacedHead) {
35 | replacement = ``;
36 | for (let i=0; i < lines.length; i++) {
37 | if (!lines[i].match(/]+)>/i, replacement);
40 | break;
41 | }
42 | }
43 |
44 | return lines.join("\n");
45 | }
46 |
47 | function requestFileJob(filePath, finish) {
48 | fs.readFile(filePath, (err, buf) => {
49 | if (err) {
50 | if (err.errno === 34) {
51 | finish(-6); // net::ERR_FILE_NOT_FOUND
52 | return;
53 | } else {
54 | finish(-2); // net::FAILED
55 | return;
56 | }
57 | }
58 |
59 | finish({
60 | data: buf,
61 | mimeType: mime.lookup(filePath) || 'text/plain'
62 | });
63 | });
64 | }
65 |
66 | let rendererInitialized = false;
67 |
68 | /**
69 | * Called by our rigged script file at the top of every HTML file to set up
70 | * the same compilers as the browser process that created us
71 | *
72 | * @private
73 | */
74 | export function initializeRendererProcess(readOnlyMode) {
75 | if (rendererInitialized) return;
76 |
77 | // NB: If we don't do this, we'll get a renderer crash if you enable debug
78 | require('debug/browser');
79 |
80 | let rootCacheDir = require('remote').getGlobal(magicGlobalForRootCacheDir);
81 | let appRoot = require('remote').getGlobal(magicGlobalForAppRootDir);
82 | let compilerHost = null;
83 |
84 | // NB: This has to be synchronous because we need to block HTML parsing
85 | // until we're set up
86 | if (readOnlyMode) {
87 | d(`Setting up electron-compile in precompiled mode with cache dir: ${rootCacheDir}`);
88 | compilerHost = CompilerHost.createReadonlyFromConfigurationSync(rootCacheDir, appRoot);
89 | } else {
90 | d(`Setting up electron-compile in development mode with cache dir: ${rootCacheDir}`);
91 | const { createCompilers } = require('./config-parser');
92 | const compilersByMimeType = createCompilers();
93 |
94 | compilerHost = CompilerHost.createFromConfigurationSync(rootCacheDir, appRoot, compilersByMimeType);
95 | }
96 |
97 | require('./x-require');
98 | require('./require-hook').default(compilerHost);
99 | rendererInitialized = true;
100 | }
101 |
102 |
103 | /**
104 | * Initializes the protocol hook on file: that allows us to intercept files
105 | * loaded by Chromium and rewrite them. This method along with
106 | * {@link registerRequireExtension} are the top-level methods that electron-compile
107 | * actually uses to intercept code that Electron loads.
108 | *
109 | * @param {CompilerHost} compilerHost The compiler host to use for compilation.
110 | */
111 | export function initializeProtocolHook(compilerHost) {
112 | protocol = protocol || require('protocol');
113 |
114 | global[magicGlobalForRootCacheDir] = compilerHost.rootCacheDir;
115 | global[magicGlobalForAppRootDir] = compilerHost.appRoot;
116 |
117 | const electronCompileSetupCode = `if (window.require) require('electron-compile/lib/protocol-hook').initializeRendererProcess(${compilerHost.readOnlyMode});`;
118 |
119 | protocol.interceptBufferProtocol('file', async function(request, finish) {
120 | let uri = url.parse(request.url);
121 |
122 | d(`Intercepting url ${request.url}`);
123 | if (request.url.indexOf(magicWords) > -1) {
124 | finish({
125 | mimeType: 'application/javascript',
126 | data: new Buffer(electronCompileSetupCode, 'utf8')
127 | });
128 |
129 | return;
130 | }
131 |
132 | // This is a protocol-relative URL that has gone pear-shaped in Electron,
133 | // let's rewrite it
134 | if (uri.host && uri.host.length > 1) {
135 | //let newUri = request.url.replace(/^file:/, "https:");
136 | // TODO: Jump off this bridge later
137 | d(`TODO: Found bogus protocol-relative URL, can't fix it up!!`);
138 | finish(-2);
139 | }
140 |
141 | let filePath = decodeURIComponent(uri.pathname);
142 |
143 | // NB: pathname has a leading '/' on Win32 for some reason
144 | if (process.platform === 'win32') {
145 | filePath = filePath.slice(1);
146 | }
147 |
148 | // NB: Special-case files coming from atom.asar or node_modules
149 | if (filePath.match(/[\/\\]atom.asar/) || filePath.match(/[\/\\]node_modules/)) {
150 | requestFileJob(filePath, finish);
151 | return;
152 | }
153 |
154 | try {
155 | let result = await compilerHost.compile(filePath);
156 |
157 | if (result.mimeType === 'text/html') {
158 | result.code = rigHtmlDocumentToInitializeElectronCompile(result.code);
159 | }
160 |
161 | if (result.binaryData || result.code instanceof Buffer) {
162 | finish({ data: result.binaryData || result.code, mimeType: result.mimeType });
163 | return;
164 | } else {
165 | finish({ data: new Buffer(result.code), mimeType: result.mimeType });
166 | return;
167 | }
168 | } catch (e) {
169 | let err = `Failed to compile ${filePath}: ${e.message}\n${e.stack}`;
170 | d(err);
171 |
172 | if (e.errno === 34 /*ENOENT*/) {
173 | finish(-6); // net::ERR_FILE_NOT_FOUND
174 | return;
175 | }
176 |
177 | finish({ mimeType: 'text/plain', data: new Buffer(err) });
178 | return;
179 | }
180 | });
181 | }
182 |
--------------------------------------------------------------------------------
/test/file-change-cache.js:
--------------------------------------------------------------------------------
1 | import './support.js';
2 |
3 | import FileChangeCache from '../src/file-change-cache';
4 | import path from 'path';
5 | import fs from 'fs';
6 | import pify from 'pify';
7 | const pfs = pify(fs);
8 |
9 | describe('The file changed cache', function() {
10 | beforeEach(function() {
11 | this.fixture = new FileChangeCache(null);
12 | });
13 |
14 | it("Correctly computes a file hash for a canned file", async function() {
15 | const expectedInfo = {
16 | hash: '4a92e95074156e8b46869519c43ddf10b59299a4',
17 | hasSourceMap: false,
18 | isInNodeModules: false,
19 | isMinified: false,
20 | isFileBinary: false
21 | };
22 |
23 | let input = path.resolve(__dirname, '..', 'test', 'fixtures', 'valid.js');
24 | let result = await this.fixture.getHashForPath(input);
25 |
26 | expect(result.sourceCode).to.be.ok;
27 | delete result.sourceCode;
28 | expect(result).to.deep.equal(expectedInfo);
29 | });
30 |
31 | it("Correctly handles binary files", async function() {
32 | const expectedInfo = {
33 | hash: '83af4f2b5a3e2dda1a322ac75799eee337d569a5',
34 | hasSourceMap: false,
35 | isInNodeModules: false,
36 | isMinified: false,
37 | isFileBinary: true
38 | };
39 |
40 | let input = path.resolve(__dirname, '..', 'test', 'fixtures', 'binaryfile.zip');
41 | let result = await this.fixture.getHashForPath(input);
42 | expect(result.binaryData).to.be.ok;
43 | expect(result.binaryData.length > 16).to.be.ok;
44 | delete result.binaryData;
45 | expect(result).to.deep.equal(expectedInfo);
46 | });
47 |
48 |
49 | it("Correctly computes a file hash for a canned file syncronously", function() {
50 | const expectedInfo = {
51 | hash: '4a92e95074156e8b46869519c43ddf10b59299a4',
52 | hasSourceMap: false,
53 | isInNodeModules: false,
54 | isMinified: false,
55 | isFileBinary: false
56 | };
57 |
58 | let input = path.resolve(__dirname, '..', 'test', 'fixtures', 'valid.js');
59 | let result = this.fixture.getHashForPathSync(input);
60 |
61 | expect(result.sourceCode).to.be.ok;
62 | delete result.sourceCode;
63 | expect(result).to.deep.equal(expectedInfo);
64 | });
65 |
66 | it("Doesn't rerun the file hash if you ask for it twice", async function() {
67 | const expectedInfo = {
68 | hash: '4a92e95074156e8b46869519c43ddf10b59299a4',
69 | hasSourceMap: false,
70 | isInNodeModules: false,
71 | isMinified: false,
72 | isFileBinary: false
73 | };
74 |
75 | let input = path.join(__dirname, '..', 'test', 'fixtures', 'valid.js');
76 | let result = await this.fixture.getHashForPath(input);
77 |
78 | expect(result.sourceCode).to.be.ok;
79 | delete result.sourceCode;
80 | expect(result).to.deep.equal(expectedInfo);
81 |
82 | this.fixture.calculateHashForFile = () => Promise.reject(new Error("Didn't work"));
83 | result = await this.fixture.getHashForPath(input);
84 |
85 | // NB: The file hash cache itself shouldn't hold onto file contents, it should
86 | // only opportunistically return it if it had to read the contents anyways
87 | expect(result.sourceCode).to.be.not.ok;
88 | expect(result).to.deep.equal(expectedInfo);
89 | });
90 |
91 | it("Throws on cache misses in production mode", function() {
92 | this.fixture = new FileChangeCache(null, true);
93 |
94 | let input = path.join(__dirname, '..', 'test', 'fixtures', 'valid.js');
95 | expect(this.fixture.getHashForPath(input)).to.eventually.throw(Error);
96 | });
97 |
98 | it("Successfully saves and loads its cache information", async function() {
99 | let input = path.join(__dirname, '..', 'test', 'fixtures', 'valid.js');
100 | await this.fixture.getHashForPath(input);
101 |
102 | let targetCache = path.join(__dirname, 'fileChangeCache1.json.gz');
103 |
104 | try {
105 | await this.fixture.save(targetCache);
106 |
107 | this.fixture = await FileChangeCache.loadFromFile(targetCache, null);
108 |
109 | this.fixture.calculateHashForFile = () => Promise.reject(new Error("Didn't work"));
110 | await this.fixture.getHashForPath(input);
111 | } finally {
112 | fs.unlinkSync(targetCache);
113 | }
114 | });
115 |
116 | it("Detects changes to files and reruns hash", async function() {
117 | const expectedInfo = {
118 | hash: '4a92e95074156e8b46869519c43ddf10b59299a4',
119 | hasSourceMap: false,
120 | isInNodeModules: false,
121 | isMinified: false,
122 | isFileBinary: false
123 | };
124 |
125 | let realInput = path.join(__dirname, '..', 'test', 'fixtures', 'valid.js');
126 | let input = path.join(__dirname, 'tempfile.tmp');
127 | let contents = await pfs.readFile(realInput);
128 | await pfs.writeFile(input, contents);
129 |
130 | let stat1 = await pfs.stat(realInput);
131 | let stat2 = await pfs.stat(input);
132 | expect(stat1.size).to.equal(stat2.size);
133 |
134 | try {
135 | let result = await this.fixture.getHashForPath(input);
136 |
137 | expect(result.sourceCode).to.be.ok;
138 | delete result.sourceCode;
139 | expect(result).to.deep.equal(expectedInfo);
140 |
141 | let fd = await pfs.open(input, 'a');
142 | await pfs.write(fd, '\n\n\n\n');
143 | await pfs.close(fd);
144 |
145 | // NB: Declaring these as 'var' works around a BabelJS compilation bug
146 | // where it can't deal with let + closure scoping
147 | var realCalc = this.fixture.calculateHashForFile;
148 | var hasCalledCalc = false;
149 |
150 | this.fixture.calculateHashForFile = function(...args) {
151 | hasCalledCalc = true;
152 | return realCalc(...args);
153 | };
154 |
155 | result = await this.fixture.getHashForPath(input);
156 |
157 | expect(result.sourceCode).to.be.ok;
158 | delete result.sourceCode;
159 |
160 | expect(result).not.to.deep.equal(expectedInfo);
161 | expect(hasCalledCalc).to.be.ok;
162 | } finally {
163 | fs.unlinkSync(input);
164 | }
165 | });
166 |
167 | it("Successfully finds if a file has a source map", async function() {
168 | let input = path.join(__dirname, '..', 'test', 'fixtures', 'source_map.js');
169 | let result = await this.fixture.getHashForPath(input);
170 |
171 | expect(result.hasSourceMap).to.be.ok;
172 | });
173 |
174 | it("Successfully finds if a file has a source map synchronously", function() {
175 | let input = path.join(__dirname, '..', 'test', 'fixtures', 'source_map.js');
176 | let result = this.fixture.getHashForPathSync(input);
177 |
178 | expect(result.hasSourceMap).to.be.ok;
179 | });
180 |
181 | it("Successfully finds if a file is minified", async function() {
182 | let input = path.join(__dirname, '..', 'test', 'fixtures', 'minified.js');
183 | let result = await this.fixture.getHashForPath(input);
184 |
185 | expect(result.isMinified).to.be.ok;
186 | });
187 |
188 | it("Successfully finds if a file is in node_modules", async function() {
189 | let input = path.join(__dirname, '..', 'node_modules', 'electron-compilers', 'package.json');
190 | let result = await this.fixture.getHashForPath(input);
191 |
192 | expect(result.isInNodeModules).to.be.ok;
193 | });
194 | });
195 |
--------------------------------------------------------------------------------
/test/config-parser.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import path from 'path';
3 | import mkdirp from 'mkdirp';
4 | import rimraf from 'rimraf';
5 |
6 | import {
7 | createCompilers,
8 | createCompilerHostFromConfiguration,
9 | createCompilerHostFromConfigFile,
10 | createCompilerHostFromBabelRc
11 | } from '../src/config-parser';
12 |
13 | const d = require('debug')('test:config-parser');
14 |
15 | let testCount = 0;
16 |
17 | describe('the configuration parser module', function() {
18 | describe('the createCompilers method', function() {
19 | it('should return compilers', function() {
20 | let result = createCompilers();
21 | expect(Object.keys(result).length > 0).to.be.ok;
22 | });
23 |
24 | it('should definitely have these compilers', function() {
25 | let result = createCompilers();
26 |
27 | expect(result['application/javascript']).to.be.ok;
28 | expect(result['text/less']).to.be.ok;
29 | });
30 | });
31 |
32 | describe('the createCompilerHostFromConfiguration method', function() {
33 | beforeEach(function() {
34 | this.tempCacheDir = path.join(__dirname, `__create_compiler_host_${testCount++}`);
35 | mkdirp.sync(this.tempCacheDir);
36 | });
37 |
38 | afterEach(function() {
39 | rimraf.sync(this.tempCacheDir);
40 | });
41 |
42 | it('respects suppressing source maps (scenario test)', async function() {
43 | let fixtureDir = path.join(__dirname, '..', 'test', 'fixtures');
44 |
45 | let result = createCompilerHostFromConfiguration({
46 | appRoot: fixtureDir,
47 | rootCacheDir: this.tempCacheDir,
48 | options: {
49 | 'application/javascript': {
50 | "presets": ["stage-0", "es2015"],
51 | "sourceMaps": false
52 | }
53 | }
54 | });
55 |
56 | let compileInfo = await result.compile(path.join(fixtureDir, 'valid.js'));
57 | d(JSON.stringify(compileInfo));
58 |
59 | expect(compileInfo.mimeType).to.equal('application/javascript');
60 |
61 | let lines = compileInfo.code.split('\n');
62 | expect(lines.length > 5).to.be.ok;
63 | expect(_.any(lines, (x) => x.match(/sourceMappingURL=/))).not.to.be.ok;
64 | });
65 | });
66 |
67 | describe('the createCompilerHostFromBabelRc method', function() {
68 | beforeEach(function() {
69 | this.tempCacheDir = path.join(__dirname, `__create_compiler_host_${testCount++}`);
70 | mkdirp.sync(this.tempCacheDir);
71 | });
72 |
73 | afterEach(function() {
74 | rimraf.sync(this.tempCacheDir);
75 | if ('BABEL_ENV' in process.env) {
76 | delete process.env.ELECTRON_COMPILE_ENV;
77 | }
78 | });
79 |
80 | it('reads from an environment-free file', async function() {
81 | let fixtureDir = path.join(__dirname, '..', 'test', 'fixtures');
82 |
83 | let result = await createCompilerHostFromBabelRc(path.join(fixtureDir, 'babelrc-noenv'));
84 |
85 | let compileInfo = await result.compile(path.join(fixtureDir, 'valid.js'));
86 | d(JSON.stringify(compileInfo));
87 |
88 | expect(compileInfo.mimeType).to.equal('application/javascript');
89 |
90 | let lines = compileInfo.code.split('\n');
91 | expect(lines.length > 5).to.be.ok;
92 | expect(_.any(lines, (x) => x.match(/sourceMappingURL=/))).to.be.ok;
93 | });
94 |
95 | it('uses the development env when env is unset', async function() {
96 | let fixtureDir = path.join(__dirname, '..', 'test', 'fixtures');
97 |
98 | let result = await createCompilerHostFromBabelRc(path.join(fixtureDir, 'babelrc-production'));
99 |
100 | let compileInfo = await result.compile(path.join(fixtureDir, 'valid.js'));
101 | d(JSON.stringify(compileInfo));
102 |
103 | expect(compileInfo.mimeType).to.equal('application/javascript');
104 |
105 | let lines = compileInfo.code.split('\n');
106 | expect(lines.length > 5).to.be.ok;
107 | expect(_.any(lines, (x) => x.match(/sourceMappingURL=/))).to.be.ok;
108 | });
109 |
110 | it('uses the production env when env is set', async function() {
111 | process.env.BABEL_ENV = 'production';
112 | let fixtureDir = path.join(__dirname, '..', 'test', 'fixtures');
113 |
114 | let result = await createCompilerHostFromBabelRc(path.join(fixtureDir, 'babelrc-production'));
115 |
116 | let compileInfo = await result.compile(path.join(fixtureDir, 'valid.js'));
117 | d(JSON.stringify(compileInfo));
118 |
119 | expect(compileInfo.mimeType).to.equal('application/javascript');
120 |
121 | let lines = compileInfo.code.split('\n');
122 | expect(lines.length > 5).to.be.ok;
123 | expect(_.any(lines, (x) => x.match(/sourceMappingURL=/))).not.to.be.ok;
124 | });
125 | });
126 |
127 | describe('the createCompilerHostFromConfigFile method', function() {
128 | beforeEach(function() {
129 | this.tempCacheDir = path.join(__dirname, `__create_compiler_host_${testCount++}`);
130 | mkdirp.sync(this.tempCacheDir);
131 | });
132 |
133 | afterEach(function() {
134 | rimraf.sync(this.tempCacheDir);
135 | if ('ELECTRON_COMPILE_ENV' in process.env) {
136 | delete process.env.ELECTRON_COMPILE_ENV;
137 | }
138 | });
139 |
140 | it('reads from an environment-free file', async function() {
141 | let fixtureDir = path.join(__dirname, '..', 'test', 'fixtures');
142 |
143 | let result = await createCompilerHostFromConfigFile(path.join(fixtureDir, 'compilerc-noenv'));
144 |
145 | let compileInfo = await result.compile(path.join(fixtureDir, 'valid.js'));
146 | d(JSON.stringify(compileInfo));
147 |
148 | expect(compileInfo.mimeType).to.equal('application/javascript');
149 |
150 | let lines = compileInfo.code.split('\n');
151 | expect(lines.length > 5).to.be.ok;
152 | expect(_.any(lines, (x) => x.match(/sourceMappingURL=/))).to.be.ok;
153 | });
154 |
155 | it('uses the development env when env is unset', async function() {
156 | let fixtureDir = path.join(__dirname, '..', 'test', 'fixtures');
157 |
158 | let result = await createCompilerHostFromConfigFile(path.join(fixtureDir, 'compilerc-production'));
159 |
160 | let compileInfo = await result.compile(path.join(fixtureDir, 'valid.js'));
161 | d(JSON.stringify(compileInfo));
162 |
163 | expect(compileInfo.mimeType).to.equal('application/javascript');
164 |
165 | let lines = compileInfo.code.split('\n');
166 | expect(lines.length > 5).to.be.ok;
167 | expect(_.any(lines, (x) => x.match(/sourceMappingURL=/))).to.be.ok;
168 | });
169 |
170 | it('uses the production env when env is set', async function() {
171 | process.env.ELECTRON_COMPILE_ENV = 'production';
172 | let fixtureDir = path.join(__dirname, '..', 'test', 'fixtures');
173 |
174 | let result = await createCompilerHostFromConfigFile(path.join(fixtureDir, 'compilerc-production'));
175 |
176 | let compileInfo = await result.compile(path.join(fixtureDir, 'valid.js'));
177 | d(JSON.stringify(compileInfo));
178 |
179 | expect(compileInfo.mimeType).to.equal('application/javascript');
180 |
181 | let lines = compileInfo.code.split('\n');
182 | expect(lines.length > 5).to.be.ok;
183 | expect(_.any(lines, (x) => x.match(/sourceMappingURL=/))).not.to.be.ok;
184 | });
185 | });
186 | });
187 |
--------------------------------------------------------------------------------
/test/fixtures/source_map.js:
--------------------------------------------------------------------------------
1 | Object.defineProperty(exports, '__esModule', {
2 | value: true
3 | });
4 |
5 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
6 |
7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
8 |
9 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
10 |
11 | var _libStore = require('../src/store');
12 |
13 | var _libStore2 = _interopRequireDefault(_libStore);
14 |
15 | var _lodash = require('lodash');
16 |
17 | var _lodash2 = _interopRequireDefault(_lodash);
18 |
19 | var _utilsFillShape = require('../utils/fill-shape');
20 |
21 | var _utilsFillShape2 = _interopRequireDefault(_utilsFillShape);
22 |
23 | var WindowStore = (function () {
24 | function WindowStore() {
25 | _classCallCheck(this, WindowStore);
26 |
27 | this.MAIN_WINDOW = 'MAIN_WINDOW';
28 | this.SINGLE_TEAM = 'SINGLE_TEAM';
29 | this.NOTIFICATIONS = 'NOTIFICATIONS';
30 | this.SSB = 'SSB';
31 | }
32 |
33 | _createClass(WindowStore, [{
34 | key: 'getWindows',
35 | value: function getWindows() {
36 | return _libStore2['default'].getEntry('windows');
37 | }
38 | }, {
39 | key: 'getWindow',
40 | value: function getWindow(id) {
41 | return this.getWindows()[id];
42 | }
43 | }, {
44 | key: 'getWindowData',
45 | value: function getWindowData(type, params) {
46 | return (0, _utilsFillShape2['default'])(_libStore2['default'].getState(), this.getWindowShape(type, params));
47 | }
48 | }, {
49 | key: 'getWindowShape',
50 | value: function getWindowShape(type, params) {
51 | switch (type) {
52 | case this.MAIN_WINDOW:
53 | return {
54 | app: true,
55 | settings: true,
56 | teams: true,
57 | events: true
58 | };
59 |
60 | case this.SINGLE_TEAM:
61 | // params=teamId
62 | var shape = {
63 | app: true,
64 | settings: true,
65 | teams: {}
66 | };
67 | shape.teams[params] = true;
68 | return shape;
69 |
70 | case this.NOTIFICATIONS:
71 | return {
72 | notifications: true,
73 | teams: true
74 | };
75 | }
76 | return {};
77 | }
78 | }, {
79 | key: 'addWindow',
80 | value: function addWindow(windowList, newWindow, type, params) {
81 | var update = {};
82 | update[newWindow.id] = {
83 | window: newWindow,
84 | type: type,
85 | params: params
86 | };
87 | return _lodash2['default'].assign({}, windowList, update);
88 | }
89 | }, {
90 | key: 'getShapeForWindow',
91 | value: function getShapeForWindow(winId) {
92 | var windowData = this.getWindows()[winId];
93 | return this.getWindowShape(windowData.type, windowData.params);
94 | }
95 | }, {
96 | key: 'reduce',
97 | value: function reduce(windows, action) {
98 | if (windows === undefined) windows = {};
99 |
100 | switch (action.type) {
101 | case 'ADD_WINDOW':
102 | return this.addWindow(windows, action.data.newWindow, action.data.windowType, action.data.params);
103 | default:
104 | return windows;
105 | }
106 | }
107 | }]);
108 |
109 | return WindowStore;
110 | })();
111 |
112 | exports['default'] = new WindowStore();
113 | module.exports = exports['default'];
114 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9Vc2Vycy9wYXVsL2NvZGUvdGlueXNwZWNrL3NsYWNrLXdpbnNzYi9zcmMvc3RvcmVzL3dpbmRvdy1zdG9yZS5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7O3dCQUFrQixjQUFjOzs7O3NCQUNsQixRQUFROzs7OzhCQUNBLHFCQUFxQjs7OztJQUVyQyxXQUFXO1dBQVgsV0FBVzswQkFBWCxXQUFXOztTQUVmLFdBQVcsR0FBRyxhQUFhO1NBQzNCLFdBQVcsR0FBRyxhQUFhO1NBQzNCLGFBQWEsR0FBRyxlQUFlO1NBQy9CLEdBQUcsR0FBRyxLQUFLOzs7ZUFMUCxXQUFXOztXQU9MLHNCQUFHO0FBQ1gsYUFBTyxzQkFBTSxRQUFRLENBQUMsU0FBUyxDQUFDLENBQUM7S0FDbEM7OztXQUVRLG1CQUFDLEVBQUUsRUFBRTtBQUNaLGFBQU8sSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0tBQzlCOzs7V0FFWSx1QkFBQyxJQUFJLEVBQUUsTUFBTSxFQUFFO0FBQzFCLGFBQU8saUNBQVUsc0JBQU0sUUFBUSxFQUFFLEVBQUUsSUFBSSxDQUFDLGNBQWMsQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQztLQUN2RTs7O1dBRWEsd0JBQUMsSUFBSSxFQUFFLE1BQU0sRUFBRTtBQUMzQixjQUFPLElBQUk7QUFDWCxhQUFLLElBQUksQ0FBQyxXQUFXO0FBQ25CLGlCQUFPO0FBQ0wsZUFBRyxFQUFFLElBQUk7QUFDVCxvQkFBUSxFQUFFLElBQUk7QUFDZCxpQkFBSyxFQUFFLElBQUk7QUFDWCxrQkFBTSxFQUFFLElBQUk7V0FDYixDQUFBOztBQUFBLEFBRUgsYUFBSyxJQUFJLENBQUMsV0FBVzs7QUFDbkIsY0FBSSxLQUFLLEdBQUc7QUFDVixlQUFHLEVBQUUsSUFBSTtBQUNULG9CQUFRLEVBQUUsSUFBSTtBQUNkLGlCQUFLLEVBQUUsRUFBRTtXQUNWLENBQUE7QUFDRCxlQUFLLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxHQUFHLElBQUksQ0FBQztBQUMzQixpQkFBTyxLQUFLLENBQUM7O0FBQUEsQUFFZixhQUFLLElBQUksQ0FBQyxhQUFhO0FBQ3JCLGlCQUFPO0FBQ0wseUJBQWEsRUFBRSxJQUFJO0FBQ25CLGlCQUFLLEVBQUUsSUFBSTtXQUNaLENBQUE7QUFBQSxPQUNGO0FBQ0QsYUFBTyxFQUFFLENBQUM7S0FDWDs7O1dBRVEsbUJBQUMsVUFBVSxFQUFFLFNBQVMsRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFO0FBQzdDLFVBQUksTUFBTSxHQUFHLEVBQUUsQ0FBQztBQUNoQixZQUFNLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQyxHQUFHO0FBQ3JCLGNBQU0sRUFBRSxTQUFTO0FBQ2pCLFlBQUksRUFBRSxJQUFJO0FBQ1YsY0FBTSxFQUFFLE1BQU07T0FDZixDQUFDO0FBQ0YsYUFBTyxvQkFBRSxNQUFNLENBQUMsRUFBRSxFQUFFLFVBQVUsRUFBRSxNQUFNLENBQUMsQ0FBQztLQUN6Qzs7O1dBRWdCLDJCQUFDLEtBQUssRUFBRTtBQUN2QixVQUFJLFVBQVUsR0FBRyxJQUFJLENBQUMsVUFBVSxFQUFFLENBQUMsS0FBSyxDQUFDLENBQUM7QUFDMUMsYUFBTyxJQUFJLENBQUMsY0FBYyxDQUFDLFVBQVUsQ0FBQyxJQUFJLEVBQUUsVUFBVSxDQUFDLE1BQU0sQ0FBQyxDQUFDO0tBQ2hFOzs7V0FFSyxnQkFBQyxPQUFPLEVBQU8sTUFBTSxFQUFFO1VBQXRCLE9BQU8sZ0JBQVAsT0FBTyxHQUFHLEVBQUU7O0FBQ2pCLGNBQU8sTUFBTSxDQUFDLElBQUk7QUFDaEIsYUFBSyxZQUFZO0FBQ2YsaUJBQU8sSUFBSSxDQUFDLFNBQVMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxVQUFVLEVBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztBQUFBLEFBQ3BHO0FBQ0UsaUJBQU8sT0FBTyxDQUFDO0FBQUEsT0FDbEI7S0FDRjs7O1NBckVHLFdBQVc7OztxQkF3RUYsSUFBSSxXQUFXLEVBQUUiLCJmaWxlIjoiL1VzZXJzL3BhdWwvY29kZS90aW55c3BlY2svc2xhY2std2luc3NiL3NyYy9zdG9yZXMvd2luZG93LXN0b3JlLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IFN0b3JlIGZyb20gJy4uL2xpYi9zdG9yZSc7XG5pbXBvcnQgXyBmcm9tICdsb2Rhc2gnO1xuaW1wb3J0IGZpbGxTaGFwZSBmcm9tICcuLi91dGlscy9maWxsLXNoYXBlJztcblxuY2xhc3MgV2luZG93U3RvcmUge1xuXG4gIE1BSU5fV0lORE9XID0gJ01BSU5fV0lORE9XJztcbiAgU0lOR0xFX1RFQU0gPSAnU0lOR0xFX1RFQU0nO1xuICBOT1RJRklDQVRJT05TID0gJ05PVElGSUNBVElPTlMnO1xuICBTU0IgPSAnU1NCJztcblxuICBnZXRXaW5kb3dzKCkge1xuICAgIHJldHVybiBTdG9yZS5nZXRFbnRyeSgnd2luZG93cycpO1xuICB9XG5cbiAgZ2V0V2luZG93KGlkKSB7XG4gICAgcmV0dXJuIHRoaXMuZ2V0V2luZG93cygpW2lkXTtcbiAgfVxuXG4gIGdldFdpbmRvd0RhdGEodHlwZSwgcGFyYW1zKSB7XG4gICAgcmV0dXJuIGZpbGxTaGFwZShTdG9yZS5nZXRTdGF0ZSgpLCB0aGlzLmdldFdpbmRvd1NoYXBlKHR5cGUsIHBhcmFtcykpO1xuICB9XG5cbiAgZ2V0V2luZG93U2hhcGUodHlwZSwgcGFyYW1zKSB7XG4gICAgc3dpdGNoKHR5cGUpIHtcbiAgICBjYXNlIHRoaXMuTUFJTl9XSU5ET1c6XG4gICAgICByZXR1cm4ge1xuICAgICAgICBhcHA6IHRydWUsXG4gICAgICAgIHNldHRpbmdzOiB0cnVlLFxuICAgICAgICB0ZWFtczogdHJ1ZSxcbiAgICAgICAgZXZlbnRzOiB0cnVlXG4gICAgICB9XG5cbiAgICBjYXNlIHRoaXMuU0lOR0xFX1RFQU06IC8vIHBhcmFtcz10ZWFtSWRcbiAgICAgIGxldCBzaGFwZSA9IHtcbiAgICAgICAgYXBwOiB0cnVlLFxuICAgICAgICBzZXR0aW5nczogdHJ1ZSxcbiAgICAgICAgdGVhbXM6IHt9XG4gICAgICB9XG4gICAgICBzaGFwZS50ZWFtc1twYXJhbXNdID0gdHJ1ZTtcbiAgICAgIHJldHVybiBzaGFwZTtcblxuICAgIGNhc2UgdGhpcy5OT1RJRklDQVRJT05TOlxuICAgICAgcmV0dXJuIHtcbiAgICAgICAgbm90aWZpY2F0aW9uczogdHJ1ZSxcbiAgICAgICAgdGVhbXM6IHRydWVcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHt9O1xuICB9XG5cbiAgYWRkV2luZG93KHdpbmRvd0xpc3QsIG5ld1dpbmRvdywgdHlwZSwgcGFyYW1zKSB7XG4gICAgbGV0IHVwZGF0ZSA9IHt9O1xuICAgIHVwZGF0ZVtuZXdXaW5kb3cuaWRdID0ge1xuICAgICAgd2luZG93OiBuZXdXaW5kb3csXG4gICAgICB0eXBlOiB0eXBlLFxuICAgICAgcGFyYW1zOiBwYXJhbXNcbiAgICB9O1xuICAgIHJldHVybiBfLmFzc2lnbih7fSwgd2luZG93TGlzdCwgdXBkYXRlKTtcbiAgfVxuXG4gIGdldFNoYXBlRm9yV2luZG93KHdpbklkKSB7XG4gICAgbGV0IHdpbmRvd0RhdGEgPSB0aGlzLmdldFdpbmRvd3MoKVt3aW5JZF07XG4gICAgcmV0dXJuIHRoaXMuZ2V0V2luZG93U2hhcGUod2luZG93RGF0YS50eXBlLCB3aW5kb3dEYXRhLnBhcmFtcyk7XG4gIH1cblxuICByZWR1Y2Uod2luZG93cyA9IHt9LCBhY3Rpb24pIHtcbiAgICBzd2l0Y2goYWN0aW9uLnR5cGUpIHtcbiAgICAgIGNhc2UgJ0FERF9XSU5ET1cnOlxuICAgICAgICByZXR1cm4gdGhpcy5hZGRXaW5kb3cod2luZG93cywgYWN0aW9uLmRhdGEubmV3V2luZG93LCBhY3Rpb24uZGF0YS53aW5kb3dUeXBlLCBhY3Rpb24uZGF0YS5wYXJhbXMpO1xuICAgICAgZGVmYXVsdDpcbiAgICAgICAgcmV0dXJuIHdpbmRvd3M7XG4gICAgfVxuICB9XG59XG5cbmV4cG9ydCBkZWZhdWx0IG5ldyBXaW5kb3dTdG9yZSgpO1xuIl19
--------------------------------------------------------------------------------
/test/compiler-host.js:
--------------------------------------------------------------------------------
1 | import './support.js';
2 |
3 | import _ from 'lodash';
4 | import path from 'path';
5 | import fs from 'fs';
6 | import rimraf from 'rimraf';
7 | import mkdirp from 'mkdirp';
8 | import mimeTypes from 'mime-types';
9 | import FileChangeCache from '../src/file-change-cache';
10 | import CompilerHost from '../src/compiler-host';
11 |
12 | const d = require('debug')('test:compiler-host');
13 |
14 | let testCount=0;
15 |
16 | describe('All available compilers', function() {
17 | it('should have a MIME type in mime-types', function() {
18 | _.each(Object.keys(global.compilersByMimeType), (type) => {
19 | d(`Extension for ${type} is ${mimeTypes.extension(type)}`);
20 | expect(mimeTypes.extension(type)).to.be.ok;
21 | });
22 | });
23 | });
24 |
25 | describe('The compiler host', function() {
26 | this.timeout(15*1000);
27 |
28 | beforeEach(function() {
29 | this.appRootDir = path.join(__dirname, '..');
30 | this.fileChangeCache = new FileChangeCache(this.appRootDir);
31 |
32 | this.tempCacheDir = path.join(__dirname, `__compile_cache_${testCount++}`);
33 | mkdirp.sync(this.tempCacheDir);
34 |
35 | this.compilersByMimeType = _.reduce(Object.keys(global.compilersByMimeType), (acc, type) => {
36 | let Klass = global.compilersByMimeType[type];
37 | acc[type] = new Klass();
38 | return acc;
39 | }, {});
40 |
41 | let InlineHtmlCompiler = Object.getPrototypeOf(this.compilersByMimeType['text/html']).constructor;
42 | this.compilersByMimeType['text/html'] = InlineHtmlCompiler.createFromCompilers(this.compilersByMimeType);
43 |
44 | this.fixture = new CompilerHost(this.tempCacheDir, this.compilersByMimeType, this.fileChangeCache, false);
45 | });
46 |
47 | afterEach(function() {
48 | rimraf.sync(this.tempCacheDir);
49 | });
50 |
51 | it('should compile basic HTML and not blow up', function() {
52 | let input = '';
53 | let inFile = path.join(this.tempCacheDir, 'input.html');
54 | fs.writeFileSync(inFile, input);
55 |
56 | let result = this.fixture.compileSync(inFile);
57 |
58 | expect(result.mimeType).to.equal('text/html');
59 | expect(result.code.length > input.length).to.be.ok;
60 | });
61 |
62 | it('Should compile everything in the fixtures directory', async function() {
63 | let input = path.join(__dirname, '..', 'test', 'fixtures');
64 |
65 | await this.fixture.compileAll(input, (filePath) => {
66 | if (filePath.match(/invalid/)) return false;
67 | if (filePath.match(/binaryfile/)) return false;
68 | if (filePath.match(/minified/)) return false;
69 | if (filePath.match(/source_map/)) return false;
70 | if (filePath.match(/babelrc/)) return false;
71 | if (filePath.match(/compilerc/)) return false;
72 |
73 | return true;
74 | });
75 | });
76 |
77 | it('Should compile everything in the fixtures directory sync', function() {
78 | let input = path.join(__dirname, '..', 'test', 'fixtures');
79 |
80 | this.fixture.compileAllSync(input, (filePath) => {
81 | if (filePath.match(/invalid/)) return false;
82 | if (filePath.match(/binaryfile/)) return false;
83 | if (filePath.match(/minified/)) return false;
84 | if (filePath.match(/source_map/)) return false;
85 | if (filePath.match(/babelrc/)) return false;
86 | if (filePath.match(/compilerc/)) return false;
87 |
88 | return true;
89 | });
90 | });
91 |
92 | it('Should read files from cache once we compile them', async function() {
93 | let input = path.join(__dirname, '..', 'test', 'fixtures');
94 |
95 | await this.fixture.compileAll(input, (filePath) => {
96 | if (filePath.match(/invalid/)) return false;
97 | if (filePath.match(/binaryfile/)) return false;
98 | if (filePath.match(/minified/)) return false;
99 | if (filePath.match(/source_map/)) return false;
100 | if (filePath.match(/babelrc/)) return false;
101 | if (filePath.match(/compilerc/)) return false;
102 |
103 | return true;
104 | });
105 |
106 | this.fixture = new CompilerHost(this.tempCacheDir, this.compilersByMimeType, this.fileChangeCache, true);
107 | this.fixture.compileUncached = () => Promise.reject(new Error("Fail!"));
108 |
109 | await this.fixture.compileAll(input, (filePath) => {
110 | if (filePath.match(/invalid/)) return false;
111 | if (filePath.match(/binaryfile/)) return false;
112 | if (filePath.match(/minified/)) return false;
113 | if (filePath.match(/source_map/)) return false;
114 | if (filePath.match(/babelrc/)) return false;
115 | if (filePath.match(/compilerc/)) return false;
116 |
117 | return true;
118 | });
119 | });
120 |
121 | it('Should read files from cache once we compile them synchronously', function() {
122 | let input = path.join(__dirname, '..', 'test', 'fixtures');
123 |
124 | this.fixture.compileAllSync(input, (filePath) => {
125 | if (filePath.match(/invalid/)) return false;
126 | if (filePath.match(/binaryfile/)) return false;
127 | if (filePath.match(/minified/)) return false;
128 | if (filePath.match(/source_map/)) return false;
129 | if (filePath.match(/babelrc/)) return false;
130 | if (filePath.match(/compilerc/)) return false;
131 |
132 | return true;
133 | });
134 |
135 | this.fixture = new CompilerHost(this.tempCacheDir, this.compilersByMimeType, this.fileChangeCache, true);
136 | this.fixture.compileUncached = () => { throw new Error("Fail!"); };
137 |
138 | this.fixture.compileAllSync(input, (filePath) => {
139 | if (filePath.match(/invalid/)) return false;
140 | if (filePath.match(/binaryfile/)) return false;
141 | if (filePath.match(/minified/)) return false;
142 | if (filePath.match(/source_map/)) return false;
143 | if (filePath.match(/babelrc/)) return false;
144 | if (filePath.match(/compilerc/)) return false;
145 |
146 | return true;
147 | });
148 | });
149 |
150 | it('Should read files from serialized compiler information', async function() {
151 | let input = path.join(__dirname, '..', 'test', 'fixtures');
152 |
153 | d("Attempting to run initial compile");
154 | await this.fixture.compileAll(input, (filePath) => {
155 | if (filePath.match(/invalid/)) return false;
156 | if (filePath.match(/binaryfile/)) return false;
157 | if (filePath.match(/minified/)) return false;
158 | if (filePath.match(/source_map/)) return false;
159 | if (filePath.match(/babelrc/)) return false;
160 | if (filePath.match(/compilerc/)) return false;
161 |
162 | return true;
163 | });
164 |
165 | d("Saving configuration");
166 | await this.fixture.saveConfiguration();
167 |
168 | d("Recreating from said configuration");
169 | this.fixture = await CompilerHost.createReadonlyFromConfiguration(this.tempCacheDir, this.appRootDir);
170 | this.fixture.compileUncached = () => Promise.reject(new Error("Fail!"));
171 |
172 | d("Recompiling everything from cached data");
173 | await this.fixture.compileAll(input, (filePath) => {
174 | if (filePath.match(/invalid/)) return false;
175 | if (filePath.match(/binaryfile/)) return false;
176 | if (filePath.match(/minified/)) return false;
177 | if (filePath.match(/source_map/)) return false;
178 | if (filePath.match(/babelrc/)) return false;
179 | if (filePath.match(/compilerc/)) return false;
180 |
181 | return true;
182 | });
183 | });
184 |
185 | it('Should read files from serialized compiler information synchronously', function() {
186 | let input = path.join(__dirname, '..', 'test', 'fixtures');
187 |
188 | d("Attempting to run initial compile");
189 | this.fixture.compileAllSync(input, (filePath) => {
190 | if (filePath.match(/invalid/)) return false;
191 | if (filePath.match(/binaryfile/)) return false;
192 | if (filePath.match(/minified/)) return false;
193 | if (filePath.match(/source_map/)) return false;
194 | if (filePath.match(/babelrc/)) return false;
195 | if (filePath.match(/compilerc/)) return false;
196 |
197 | return true;
198 | });
199 |
200 | d("Saving configuration");
201 | this.fixture.saveConfigurationSync();
202 |
203 | d("Recreating from said configuration");
204 | this.fixture = CompilerHost.createReadonlyFromConfigurationSync(this.tempCacheDir, this.appRootDir);
205 | this.fixture.compileUncached = () => Promise.reject(new Error("Fail!"));
206 |
207 | d("Recompiling everything from cached data");
208 | this.fixture.compileAllSync(input, (filePath) => {
209 | if (filePath.match(/invalid/)) return false;
210 | if (filePath.match(/binaryfile/)) return false;
211 | if (filePath.match(/minified/)) return false;
212 | if (filePath.match(/source_map/)) return false;
213 | if (filePath.match(/babelrc/)) return false;
214 | if (filePath.match(/compilerc/)) return false;
215 |
216 | return true;
217 | });
218 | });
219 | });
220 |
--------------------------------------------------------------------------------
/src/compile-cache.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import zlib from 'zlib';
4 | import createDigestForObject from './digest-for-object';
5 | import {pfs, pzlib} from './promise';
6 | import mkdirp from 'mkdirp';
7 |
8 | const d = require('debug')('electron-compile:compile-cache');
9 |
10 | /**
11 | * CompileCache manages getting and setting entries for a single compiler; each
12 | * in-use compiler will have an instance of this class, usually created via
13 | * {@link createFromCompiler}.
14 | *
15 | * You usually will not use this class directly, it is an implementation class
16 | * for {@link CompileHost}.
17 | */
18 | export default class CompileCache {
19 | /**
20 | * Creates an instance, usually used for testing only.
21 | *
22 | * @param {string} cachePath The root directory to use as a cache path
23 | *
24 | * @param {FileChangedCache} fileChangeCache A file-change cache that is
25 | * optionally pre-loaded.
26 | */
27 | constructor(cachePath, fileChangeCache) {
28 | this.cachePath = cachePath;
29 | this.fileChangeCache = fileChangeCache;
30 | }
31 |
32 | /**
33 | * Creates a CompileCache from a class compatible with the CompilerBase
34 | * interface. This method uses the compiler name / version / options to
35 | * generate a unique directory name for cached results
36 | *
37 | * @param {string} cachePath The root path to use for the cache, a directory
38 | * representing the hash of the compiler parameters
39 | * will be created here.
40 | *
41 | * @param {CompilerBase} compiler The compiler to use for version / option
42 | * information.
43 | *
44 | * @param {FileChangedCache} fileChangeCache A file-change cache that is
45 | * optionally pre-loaded.
46 | *
47 | * @param {boolean} readOnlyMode Don't attempt to create the cache directory.
48 | *
49 | * @return {CompileCache} A configured CompileCache instance.
50 | */
51 | static createFromCompiler(cachePath, compiler, fileChangeCache, readOnlyMode=false) {
52 | let newCachePath = null;
53 | let getCachePath = () => {
54 | if (newCachePath) return newCachePath;
55 |
56 | const digestObj = {
57 | name: compiler.name || Object.getPrototypeOf(compiler).constructor.name,
58 | version: compiler.getCompilerVersion(),
59 | options: compiler.compilerOptions
60 | };
61 |
62 | newCachePath = path.join(cachePath, createDigestForObject(digestObj));
63 |
64 | d(`Path for ${digestObj.name}: ${newCachePath}`);
65 | d(`Set up with parameters: ${JSON.stringify(digestObj)}`);
66 |
67 | if (!readOnlyMode) mkdirp.sync(newCachePath);
68 | return newCachePath;
69 | };
70 |
71 | let ret = new CompileCache('', fileChangeCache);
72 | ret.getCachePath = getCachePath;
73 |
74 | return ret;
75 | }
76 |
77 | /**
78 | * Returns a file's compiled contents from the cache.
79 | *
80 | * @param {string} filePath The path to the file. FileChangedCache will look
81 | * up the hash and use that as the key in the cache.
82 | *
83 | * @return {Promise