├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── README.md ├── esdoc.json ├── package.json └── src ├── compiler-base.js ├── css ├── less.js ├── sass.js └── stylus.js ├── html ├── inline-html.js ├── jade.js └── vue.js ├── js ├── babel.js ├── coffeescript.js ├── graphql.js └── typescript.js ├── json └── cson.js ├── main.js └── passthrough.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2016-node5", "react"], 3 | "plugins": ["transform-async-to-generator"], 4 | "sourceMaps": "inline" 5 | } 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "rules": { 4 | "strict": 0, 5 | "indent": [ 6 | 2, 7 | 2 8 | ], 9 | "semi": [ 10 | 2, 11 | "always" 12 | ], 13 | "no-console": 0 14 | }, 15 | "env": { 16 | "es6": true, 17 | "node": true, 18 | "browser": true 19 | }, 20 | "extends": "eslint:recommended" 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 27 | node_modules 28 | 29 | lib 30 | test-dist 31 | docs 32 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test-dist/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '5' 4 | - '6' 5 | - '7' 6 | 7 | script: 8 | - npm run lint 9 | - npm run compile 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## DEPRECATED: electron-compilers 2 | 3 | [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) 4 | 5 | This project is no longer maintained, pull requests are no longer being reviewed or merged and issues are no longer being responded to. 6 | 7 | --- 8 | 9 | [![](https://electron-userland.github.io/electron-compilers/docs/badge.svg)](https://electron-userland.github.io/electron-compilers/docs) 10 | 11 | electron-compilers are the actual implementations of classes that power 12 | [electron-compile](https://github.com/electron-userland/electron-compile) 13 | 14 | For JavaScript: 15 | 16 | * JavaScript ES6/ES7 (via Babel) 17 | * TypeScript 18 | * CoffeeScript 19 | * GraphQL 20 | 21 | For CSS: 22 | 23 | * LESS 24 | * Stylus (with optional nib) 25 | 26 | For HTML: 27 | 28 | * Jade 29 | 30 | For JSON: 31 | 32 | * CSON 33 | 34 | ### Why is this a separate repo?! 35 | 36 | Shipping compilers for all of these languages will add a ton of weight to your 37 | download size. Making this a separate top-level module allows you to mark it 38 | as a `devDependency` and not include it in the final app. 39 | -------------------------------------------------------------------------------- /esdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": "./src", 3 | "destination": "./docs", 4 | "includes": ["\\.(js|es6)$"], 5 | "excludes": ["\\.config\\.(js|es6)$"], 6 | "access": ["public", "protected"], 7 | "autoPrivate": true, 8 | "unexportIdentifier": false, 9 | "undocumentIdentifier": true, 10 | "builtinExternal": true, 11 | "index": "./README.md", 12 | "package": "./package.json", 13 | "coverage": true, 14 | "includeSource": true, 15 | "title": "electron-compilers", 16 | "plugins": [ 17 | {"name": "esdoc-es7-plugin"}, 18 | {"name": "esdoc-plugin-async-to-sync"} 19 | ], 20 | "lint": true 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-compilers", 3 | "version": "5.9.0", 4 | "description": "Compiler implementations for electron-compile", 5 | "main": "lib/main.js", 6 | "scripts": { 7 | "doc": "esdoc -c ./esdoc.json", 8 | "compile": "git clean -xdf lib && babel -d lib/ src/", 9 | "lint": "eslint src", 10 | "prepublish": "npm run compile" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/paulcbetts/electron-compilers" 15 | }, 16 | "keywords": [ 17 | "electron" 18 | ], 19 | "author": "Paul Betts ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/paulcbetts/electron-compilers/issues" 23 | }, 24 | "homepage": "https://github.com/paulcbetts/electron-compilers", 25 | "engines": { 26 | "node": ">= 5.0" 27 | }, 28 | "dependencies": { 29 | "@paulcbetts/mime-types": "^2.1.10", 30 | "@paulcbetts/vueify": "9.4.3", 31 | "babel-core": "^6.13.2", 32 | "babel-preset-env": "^1.3.2", 33 | "btoa": "^1.1.2", 34 | "cheerio": "^0.20.0", 35 | "coffee-script": "^1.10.0", 36 | "cson": "^3.0.2", 37 | "debug": "^2.5.1", 38 | "detective-less": "^1.0.0", 39 | "detective-sass": "^2.0.0", 40 | "detective-scss": "^1.0.0", 41 | "detective-stylus": "^1.0.0", 42 | "graphql": "^0.9.3", 43 | "graphql-tag": "^2.0.0", 44 | "istanbul": "^0.4.5", 45 | "jade": "^1.11.0", 46 | "js-string-escape": "^1.0.1", 47 | "less": "^2.7.1", 48 | "mkdirp": "^0.5.1", 49 | "nib": "^1.1.2", 50 | "resolve": "^1.2.0", 51 | "rimraf": "^2.5.4", 52 | "sass-lookup": "^1.0.2", 53 | "sass.js": "^0.10.1", 54 | "sorcery": "^0.10.0", 55 | "stylus": "^0.54.5", 56 | "stylus-lookup": "^1.0.1", 57 | "toutsuite": "^0.6.0", 58 | "typescript": ">=1.6" 59 | }, 60 | "devDependencies": { 61 | "babel-cli": "^6.11.4", 62 | "babel-eslint": "^6.1.2", 63 | "babel-plugin-transform-async-to-generator": "^6.8.0", 64 | "babel-plugin-transform-object-rest-spread": "^6.8.0", 65 | "babel-plugin-transform-runtime": "^6.12.0", 66 | "babel-preset-es2016-node5": "^1.1.2", 67 | "babel-preset-react": "^6.11.1", 68 | "babel-register": "^6.18.0", 69 | "esdoc": "^0.4.8", 70 | "esdoc-es7-plugin": "0.0.3", 71 | "esdoc-plugin-async-to-sync": "^0.5.0", 72 | "eslint": "^3.3.0" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/compiler-base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This class is the base interface for compilers that are used by 3 | * electron-compile. If your compiler library only supports a 4 | * synchronous API, use SimpleCompilerBase instead. 5 | * 6 | * @interface 7 | */ 8 | export class CompilerBase { 9 | constructor() { 10 | this.compilerOptions = {}; 11 | } 12 | 13 | /** 14 | * This method describes the MIME types that your compiler supports as input. 15 | * Many precompiled file types don't have a specific MIME type, so if it's not 16 | * recognized by the mime-types package, you need to patch rig-mime-types in 17 | * electron-compile. 18 | * 19 | * @return {string[]} An array of MIME types that this compiler can compile. 20 | * 21 | * @abstract 22 | */ 23 | static getInputMimeTypes() { 24 | throw new Error("Implement me!"); 25 | } 26 | 27 | 28 | /** 29 | * Determines whether a file should be compiled 30 | * 31 | * @param {string} fileName The full path of a file to compile. 32 | * @param {object} compilerContext An object that compilers can add extra 33 | information to as part of a job - the caller 34 | won't do anything with this. 35 | * @return {Promise} True if you are able to compile this file. 36 | * 37 | * @abstract 38 | */ 39 | async shouldCompileFile(fileName, compilerContext) { // eslint-disable-line no-unused-vars 40 | throw new Error("Implement me!"); 41 | } 42 | 43 | 44 | /** 45 | * Returns the dependent files of this file. This is used for languages such 46 | * as LESS which allow you to import / reference other related files. In future 47 | * versions of electron-compile, we will use this information to invalidate 48 | * all of the parent files if a child file changes. 49 | * 50 | * @param {string} sourceCode The contents of filePath 51 | * @param {string} fileName The full path of a file to compile. 52 | * @param {object} compilerContext An object that compilers can add extra 53 | information to as part of a job - the caller 54 | won't do anything with this. 55 | * @return {Promise} An array of dependent file paths, or an empty 56 | * array if there are no dependent files. 57 | * 58 | * @abstract 59 | */ 60 | async determineDependentFiles(sourceCode, fileName, compilerContext) { // eslint-disable-line no-unused-vars 61 | throw new Error("Implement me!"); 62 | } 63 | 64 | 65 | /** 66 | * Compiles the file 67 | * 68 | * @param {string} sourceCode The contents of filePath 69 | * @param {string} fileName The full path of a file to compile. 70 | * @param {object} compilerContext An object that compilers can add extra 71 | information to as part of a job - the caller 72 | won't do anything with this. 73 | * @return {Promise} An object representing the compiled result 74 | * @property {string} code The compiled code 75 | * @property {string} mimeType The MIME type of the compiled result, which 76 | * should exist in the mime-types database. 77 | * 78 | * @abstract 79 | */ 80 | async compile(sourceCode, fileName, compilerContext) { // eslint-disable-line no-unused-vars 81 | throw new Error("Implement me!"); 82 | } 83 | 84 | shouldCompileFileSync(fileName, compilerContext) { // eslint-disable-line no-unused-vars 85 | throw new Error("Implement me!"); 86 | } 87 | 88 | determineDependentFilesSync(sourceCode, fileName, compilerContext) { // eslint-disable-line no-unused-vars 89 | throw new Error("Implement me!"); 90 | } 91 | 92 | compileSync(sourceCode, fileName, compilerContext) { // eslint-disable-line no-unused-vars 93 | throw new Error("Implement me!"); 94 | } 95 | 96 | /** 97 | * Returns a version number representing the version of the underlying 98 | * compiler library. When this number changes, electron-compile knows 99 | * to throw all away its generated code. 100 | * 101 | * @return {string} A version number. Note that this string isn't 102 | * parsed in any way, just compared to the previous 103 | * one for equality. 104 | * 105 | * @abstract 106 | */ 107 | getCompilerVersion() { 108 | throw new Error("Implement me!"); 109 | } 110 | } 111 | 112 | 113 | /** 114 | * This class implements all of the async methods of CompilerBase by just 115 | * calling the sync version. Use it to save some time when implementing 116 | * simple compilers. 117 | * 118 | * To use it, implement the compile method, the getCompilerVersion method, 119 | * and the getInputMimeTypes static method. 120 | * 121 | * @abstract 122 | */ 123 | export class SimpleCompilerBase extends CompilerBase { 124 | constructor() { 125 | super(); 126 | } 127 | 128 | async shouldCompileFile(fileName, compilerContext) { // eslint-disable-line no-unused-vars 129 | return true; 130 | } 131 | 132 | async determineDependentFiles(sourceCode, filePath, compilerContext) { // eslint-disable-line no-unused-vars 133 | return []; 134 | } 135 | 136 | async compile(sourceCode, filePath, compilerContext) { 137 | return this.compileSync(sourceCode, filePath, compilerContext); 138 | } 139 | 140 | shouldCompileFileSync(fileName, compilerContext) { // eslint-disable-line no-unused-vars 141 | return true; 142 | } 143 | 144 | determineDependentFilesSync(sourceCode, filePath, compilerContext) { // eslint-disable-line no-unused-vars 145 | return []; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/css/less.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import detective from 'detective-less'; 3 | import {CompilerBase} from '../compiler-base'; 4 | import toutSuite from 'toutsuite'; 5 | 6 | const mimeTypes = ['text/less']; 7 | let lessjs = null; 8 | 9 | /** 10 | * @access private 11 | */ 12 | export default class LessCompiler extends CompilerBase { 13 | constructor() { 14 | super(); 15 | 16 | this.compilerOptions = { 17 | sourceMap: { sourceMapFileInline: true } 18 | }; 19 | 20 | this.seenFilePaths = {}; 21 | } 22 | 23 | static getInputMimeTypes() { 24 | return mimeTypes; 25 | } 26 | 27 | async shouldCompileFile(fileName, compilerContext) { // eslint-disable-line no-unused-vars 28 | return true; 29 | } 30 | 31 | async determineDependentFiles(sourceCode, filePath, compilerContext) { 32 | return this.determineDependentFilesSync(sourceCode, filePath, compilerContext); 33 | } 34 | 35 | async compile(sourceCode, filePath, compilerContext) { // eslint-disable-line no-unused-vars 36 | lessjs = lessjs || this.getLess(); 37 | 38 | let thisPath = path.dirname(filePath); 39 | this.seenFilePaths[thisPath] = true; 40 | 41 | let paths = Object.keys(this.seenFilePaths); 42 | 43 | if (this.compilerOptions.paths) { 44 | paths.push(...this.compilerOptions.paths); 45 | } 46 | 47 | let opts = Object.assign({}, this.compilerOptions, { 48 | paths: paths, 49 | filename: path.basename(filePath) 50 | }); 51 | 52 | let result = await lessjs.render(sourceCode, opts); 53 | let source = result.css; 54 | 55 | // NB: If you compile a file that is solely imports, its 56 | // actual content is '' yet it is a valid file. '' is not 57 | // truthy, so we're going to replace it with a string that 58 | // is truthy. 59 | if (!source && typeof source === 'string') { 60 | source = ' '; 61 | } 62 | 63 | return { 64 | code: source, 65 | mimeType: 'text/css' 66 | }; 67 | } 68 | 69 | shouldCompileFileSync(fileName, compilerContext) { // eslint-disable-line no-unused-vars 70 | return true; 71 | } 72 | 73 | determineDependentFilesSync(sourceCode, filePath, compilerContext) { // eslint-disable-line no-unused-vars 74 | let dependencyFilenames = detective(sourceCode); 75 | let dependencies = []; 76 | 77 | for (let dependencyName of dependencyFilenames) { 78 | dependencies.push(path.join(path.dirname(filePath), dependencyName)); 79 | } 80 | 81 | return dependencies; 82 | } 83 | 84 | compileSync(sourceCode, filePath, compilerContext) { // eslint-disable-line no-unused-vars 85 | lessjs = lessjs || this.getLess(); 86 | 87 | let source; 88 | let error = null; 89 | 90 | let thisPath = path.dirname(filePath); 91 | this.seenFilePaths[thisPath] = true; 92 | 93 | let paths = Object.keys(this.seenFilePaths); 94 | 95 | if (this.compilerOptions.paths) { 96 | paths.push(...this.compilerOptions.paths); 97 | } 98 | 99 | let opts = Object.assign({}, this.compilerOptions, { 100 | paths: paths, 101 | filename: path.basename(filePath), 102 | fileAsync: false, async: false, syncImport: true 103 | }); 104 | 105 | toutSuite(() => { 106 | lessjs.render(sourceCode, opts, (err, out) => { 107 | if (err) { 108 | error = err; 109 | } else { 110 | // NB: Because we've forced less to work in sync mode, we can do this 111 | source = out.css; 112 | } 113 | }); 114 | }); 115 | 116 | if (error) { 117 | throw error; 118 | } 119 | 120 | // NB: If you compile a file that is solely imports, its 121 | // actual content is '' yet it is a valid file. '' is not 122 | // truthy, so we're going to replace it with a string that 123 | // is truthy. 124 | if (!source && typeof source === 'string') { 125 | source = ' '; 126 | } 127 | 128 | return { 129 | code: source, 130 | mimeType: 'text/css' 131 | }; 132 | } 133 | 134 | getLess() { 135 | let ret; 136 | toutSuite(() => ret = require('less')); 137 | return ret; 138 | } 139 | 140 | getCompilerVersion() { 141 | return require('less/package.json').version; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/css/sass.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | import toutSuite from 'toutsuite'; 4 | import detectiveSASS from 'detective-sass'; 5 | import detectiveSCSS from 'detective-scss'; 6 | import sassLookup from 'sass-lookup'; 7 | import {CompilerBase} from '../compiler-base'; 8 | 9 | const mimeTypes = ['text/sass', 'text/scss']; 10 | let sass = null; 11 | 12 | /** 13 | * @access private 14 | */ 15 | export default class SassCompiler extends CompilerBase { 16 | constructor() { 17 | super(); 18 | 19 | this.compilerOptions = { 20 | comments: true, 21 | sourceMapEmbed: true, 22 | sourceMapContents: true 23 | }; 24 | 25 | this.seenFilePaths = {}; 26 | } 27 | 28 | static getInputMimeTypes() { 29 | return mimeTypes; 30 | } 31 | 32 | async shouldCompileFile(fileName, compilerContext) { // eslint-disable-line no-unused-vars 33 | return true; 34 | } 35 | 36 | async determineDependentFiles(sourceCode, filePath, compilerContext) { 37 | return this.determineDependentFilesSync(sourceCode, filePath, compilerContext); 38 | } 39 | 40 | async compile(sourceCode, filePath, compilerContext) { // eslint-disable-line no-unused-vars 41 | sass = sass || this.getSass(); 42 | 43 | let thisPath = path.dirname(filePath); 44 | this.seenFilePaths[thisPath] = true; 45 | 46 | let paths = Object.keys(this.seenFilePaths); 47 | 48 | if (this.compilerOptions.paths) { 49 | paths.push(...this.compilerOptions.paths); 50 | } 51 | 52 | paths.unshift('.'); 53 | 54 | sass.importer(this.buildImporterCallback(paths)); 55 | 56 | let opts = Object.assign({}, this.compilerOptions, { 57 | indentedSyntax: filePath.match(/\.sass$/i), 58 | sourceMapRoot: filePath, 59 | }); 60 | 61 | let result = await new Promise((res,rej) => { 62 | sass.compile(sourceCode, opts, (r) => { 63 | if (r.status !== 0) { 64 | rej(new Error(r.formatted || r.message)); 65 | return; 66 | } 67 | 68 | res(r); 69 | return; 70 | }); 71 | }); 72 | 73 | let source = result.text; 74 | 75 | // NB: If you compile a file that is solely imports, its 76 | // actual content is '' yet it is a valid file. '' is not 77 | // truthy, so we're going to replace it with a string that 78 | // is truthy. 79 | if (!source) { 80 | source = ' '; 81 | } 82 | 83 | return { 84 | code: source, 85 | mimeType: 'text/css' 86 | }; 87 | } 88 | 89 | shouldCompileFileSync(fileName, compilerContext) { // eslint-disable-line no-unused-vars 90 | return true; 91 | } 92 | 93 | determineDependentFilesSync(sourceCode, filePath, compilerContext) { // eslint-disable-line no-unused-vars 94 | let dependencyFilenames = path.extname(filePath) === '.sass' ? detectiveSASS(sourceCode) : detectiveSCSS(sourceCode); 95 | let dependencies = []; 96 | 97 | for (let dependencyName of dependencyFilenames) { 98 | dependencies.push(sassLookup(dependencyName, path.basename(filePath), path.dirname(filePath))); 99 | } 100 | 101 | return dependencies; 102 | } 103 | 104 | compileSync(sourceCode, filePath, compilerContext) { // eslint-disable-line no-unused-vars 105 | sass = sass || this.getSass(); 106 | 107 | let thisPath = path.dirname(filePath); 108 | this.seenFilePaths[thisPath] = true; 109 | 110 | let paths = Object.keys(this.seenFilePaths); 111 | 112 | if (this.compilerOptions.paths) { 113 | paths.push(...this.compilerOptions.paths); 114 | } 115 | 116 | paths.unshift('.'); 117 | sass.importer(this.buildImporterCallback(paths)); 118 | 119 | let opts = Object.assign({}, this.compilerOptions, { 120 | indentedSyntax: filePath.match(/\.sass$/i), 121 | sourceMapRoot: filePath, 122 | }); 123 | 124 | let result; 125 | toutSuite(() => { 126 | sass.compile(sourceCode, opts, (r) => { 127 | if (r.status !== 0) { 128 | throw new Error(r.formatted); 129 | } 130 | result = r; 131 | }); 132 | }); 133 | 134 | let source = result.text; 135 | 136 | // NB: If you compile a file that is solely imports, its 137 | // actual content is '' yet it is a valid file. '' is not 138 | // truthy, so we're going to replace it with a string that 139 | // is truthy. 140 | if (!source) { 141 | source = ' '; 142 | } 143 | 144 | return { 145 | code: source, 146 | mimeType: 'text/css' 147 | }; 148 | } 149 | 150 | getSass() { 151 | let ret; 152 | toutSuite(() => ret = require('sass.js/dist/sass.node').Sass); 153 | return ret; 154 | } 155 | 156 | buildImporterCallback (includePaths) { 157 | const self = this; 158 | return (function (request, done) { 159 | let file; 160 | if (request.file) { 161 | done(); 162 | return; 163 | } else { 164 | // sass.js works in the '/sass/' directory 165 | const cleanedRequestPath = request.resolved.replace(/^\/sass\//, ''); 166 | for (let includePath of includePaths) { 167 | const filePath = path.resolve(includePath, cleanedRequestPath); 168 | let variations = sass.getPathVariations(filePath); 169 | 170 | file = variations 171 | .map(self.fixWindowsPath.bind(self)) 172 | .reduce(self.importedFileReducer.bind(self), null); 173 | 174 | if (file) { 175 | const content = fs.readFileSync(file, { encoding: 'utf8' }); 176 | return sass.writeFile(file, content, () => { 177 | done({ path: file }); 178 | return; 179 | }); 180 | } 181 | } 182 | 183 | if (!file) { 184 | done(); 185 | return; 186 | } 187 | } 188 | }); 189 | } 190 | 191 | importedFileReducer(found, path) { 192 | // Find the first variation that actually exists 193 | if (found) return found; 194 | 195 | try { 196 | const stat = fs.statSync(path); 197 | if (!stat.isFile()) return null; 198 | return path; 199 | } catch(e) { 200 | return null; 201 | } 202 | } 203 | 204 | fixWindowsPath(file) { 205 | // Unfortunately, there's a bug in sass.js that seems to ignore the different 206 | // path separators across platforms 207 | 208 | // For some reason, some files have a leading slash that we need to get rid of 209 | if (process.platform === 'win32' && file[0] === '/') { 210 | file = file.slice(1); 211 | } 212 | 213 | // Sass.js generates paths such as `_C:\myPath\file.sass` instead of `C:\myPath\_file.sass` 214 | if (file[0] === '_') { 215 | const parts = file.slice(1).split(path.sep); 216 | const dir = parts.slice(0, -1).join(path.sep); 217 | const fileName = parts.reverse()[0]; 218 | file = path.resolve(dir, '_' + fileName); 219 | } 220 | return file; 221 | } 222 | 223 | getCompilerVersion() { 224 | // NB: There is a bizarre bug in the node module system where this doesn't 225 | // work but only in saveConfiguration tests 226 | //return require('@paulcbetts/node-sass/package.json').version; 227 | return "4.1.1"; 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/css/stylus.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import detective from 'detective-stylus'; 3 | import lookup from 'stylus-lookup'; 4 | import {CompilerBase} from '../compiler-base'; 5 | import {basename} from 'path'; 6 | 7 | const mimeTypes = ['text/stylus']; 8 | 9 | let stylusjs = null; 10 | let nib = null; 11 | 12 | function each(obj, sel) { 13 | for (let k in obj) { 14 | sel(obj[k], k); 15 | } 16 | } 17 | 18 | /** 19 | * @access private 20 | */ 21 | export default class StylusCompiler extends CompilerBase { 22 | constructor() { 23 | super(); 24 | 25 | this.compilerOptions = { 26 | sourcemap: 'inline', 27 | import: ['nib'] 28 | }; 29 | 30 | this.seenFilePaths = {}; 31 | } 32 | 33 | static getInputMimeTypes() { 34 | return mimeTypes; 35 | } 36 | 37 | async shouldCompileFile(fileName, compilerContext) { // eslint-disable-line no-unused-vars 38 | return true; 39 | } 40 | 41 | async determineDependentFiles(sourceCode, filePath, compilerContext) { // eslint-disable-line no-unused-vars 42 | return this.determineDependentFilesSync(sourceCode, filePath, compilerContext); 43 | } 44 | 45 | async compile(sourceCode, filePath, compilerContext) { // eslint-disable-line no-unused-vars 46 | nib = nib || require('nib'); 47 | stylusjs = stylusjs || require('stylus'); 48 | this.seenFilePaths[path.dirname(filePath)] = true; 49 | 50 | let opts = this.makeOpts(filePath); 51 | 52 | let code = await new Promise((res,rej) => { 53 | let styl = stylusjs(sourceCode, opts); 54 | 55 | this.applyOpts(opts, styl); 56 | 57 | styl.render((err, css) => { 58 | if (err) { 59 | rej(err); 60 | } else { 61 | res(css); 62 | } 63 | }); 64 | }); 65 | 66 | return { 67 | code, mimeType: 'text/css' 68 | }; 69 | } 70 | 71 | makeOpts(filePath) { 72 | let opts = Object.assign({}, this.compilerOptions, { 73 | filename: basename(filePath) 74 | }); 75 | 76 | if (opts.import && !Array.isArray(opts.import)) { 77 | opts.import = [opts.import]; 78 | } 79 | 80 | if (opts.import && opts.import.indexOf('nib') >= 0) { 81 | opts.use = opts.use || []; 82 | 83 | if (!Array.isArray(opts.use)) { 84 | opts.use = [opts.use]; 85 | } 86 | 87 | opts.use.push(nib()); 88 | } 89 | 90 | return opts; 91 | } 92 | 93 | 94 | applyOpts(opts, stylus) { 95 | each(opts, (val, key) => { 96 | switch(key) { 97 | case 'set': 98 | case 'define': 99 | each(val, (v, k) => stylus[key](k, v)); 100 | break; 101 | case 'include': 102 | case 'import': 103 | case 'use': 104 | each(val, (v) => stylus[key](v)); 105 | break; 106 | } 107 | }); 108 | 109 | stylus.set('paths', Object.keys(this.seenFilePaths).concat(['.'])); 110 | } 111 | 112 | shouldCompileFileSync(fileName, compilerContext) { // eslint-disable-line no-unused-vars 113 | return true; 114 | } 115 | 116 | determineDependentFilesSync(sourceCode, filePath, compilerContext) { // eslint-disable-line no-unused-vars 117 | let dependencyFilenames = detective(sourceCode); 118 | let dependencies = []; 119 | 120 | for (let dependencyName of dependencyFilenames) { 121 | dependencies.push(lookup(dependencyName, path.basename(filePath), path.dirname(filePath))); 122 | } 123 | 124 | return dependencies; 125 | } 126 | 127 | compileSync(sourceCode, filePath, compilerContext) { // eslint-disable-line no-unused-vars 128 | nib = nib || require('nib'); 129 | stylusjs = stylusjs || require('stylus'); 130 | this.seenFilePaths[path.dirname(filePath)] = true; 131 | 132 | let opts = this.makeOpts(filePath), styl = stylusjs(sourceCode, opts); 133 | 134 | this.applyOpts(opts, styl); 135 | 136 | return { 137 | code: styl.render(), 138 | mimeType: 'text/css' 139 | }; 140 | } 141 | 142 | getCompilerVersion() { 143 | return require('stylus/package.json').version; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/html/inline-html.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import mimeTypes from '@paulcbetts/mime-types'; 3 | import {CompilerBase} from '../compiler-base'; 4 | 5 | const inputMimeTypes = ['text/html']; 6 | let cheerio = null; 7 | 8 | const d = require('debug')('electron-compile:inline-html'); 9 | 10 | const compiledCSS = { 11 | 'text/less': true, 12 | 'text/scss': true, 13 | 'text/sass': true, 14 | 'text/stylus': true, 15 | }; 16 | 17 | /** 18 | * @access private 19 | */ 20 | export default class InlineHtmlCompiler extends CompilerBase { 21 | constructor(compileBlock, compileBlockSync) { 22 | super(); 23 | 24 | this.compileBlock = compileBlock; 25 | this.compileBlockSync = compileBlockSync; 26 | } 27 | 28 | static createFromCompilers(compilersByMimeType) { 29 | d(`Setting up inline HTML compilers: ${JSON.stringify(Object.keys(compilersByMimeType))}`); 30 | 31 | let compileBlock = async (sourceCode, filePath, mimeType, ctx) => { 32 | let realType = mimeType; 33 | if (!mimeType && ctx.tag === 'script') realType = 'application/javascript'; 34 | 35 | if (!realType) return sourceCode; 36 | 37 | let compiler = compilersByMimeType[realType] || compilersByMimeType['text/plain']; 38 | let ext = mimeTypes.extension(realType); 39 | let fakeFile = `${filePath}:inline_${ctx.count}.${ext}`; 40 | 41 | d(`Compiling inline block for ${filePath} with mimeType ${mimeType}`); 42 | if (!(await compiler.shouldCompileFile(fakeFile, ctx))) return sourceCode; 43 | return (await compiler.compileSync(sourceCode, fakeFile, ctx)).code; 44 | }; 45 | 46 | let compileBlockSync = (sourceCode, filePath, mimeType, ctx) => { 47 | let realType = mimeType; 48 | if (!mimeType && ctx.tag === 'script') realType = 'application/javascript'; 49 | 50 | if (!realType) return sourceCode; 51 | 52 | let compiler = compilersByMimeType[realType] || compilersByMimeType['text/plain']; 53 | let ext = mimeTypes.extension(realType); 54 | let fakeFile = `${filePath}:inline_${ctx.count}.${ext}`; 55 | 56 | d(`Compiling inline block for ${filePath} with mimeType ${mimeType}`); 57 | if (!compiler.shouldCompileFileSync(fakeFile, ctx)) return sourceCode; 58 | return compiler.compileSync(sourceCode, fakeFile, ctx).code; 59 | }; 60 | 61 | return new InlineHtmlCompiler(compileBlock, compileBlockSync); 62 | } 63 | 64 | static getInputMimeTypes() { 65 | return inputMimeTypes; 66 | } 67 | 68 | async shouldCompileFile(fileName, compilerContext) { // eslint-disable-line no-unused-vars 69 | return true; 70 | } 71 | 72 | async determineDependentFiles(sourceCode, filePath, compilerContext) { // eslint-disable-line no-unused-vars 73 | return []; 74 | } 75 | 76 | async each(nodes, selector) { 77 | let acc = []; 78 | nodes.each((i, el) => { 79 | let promise = selector(i,el); 80 | if (!promise) return false; 81 | 82 | acc.push(promise); 83 | return true; 84 | }); 85 | 86 | await Promise.all(acc); 87 | } 88 | 89 | eachSync(nodes, selector) { 90 | // NB: This method is here just so it's easier to mechanically 91 | // translate the async compile to compileSync 92 | return nodes.each((i,el) => { 93 | selector(i,el); 94 | return true; 95 | }); 96 | } 97 | 98 | async compile(sourceCode, filePath, compilerContext) { 99 | cheerio = cheerio || require('cheerio'); 100 | 101 | //Leave the attributes casing as it is, because of Angular 2 and maybe other case-sensitive frameworks 102 | let $ = cheerio.load(sourceCode, {lowerCaseAttributeNames: false}); 103 | let toWait = []; 104 | 105 | let that = this; 106 | let styleCount = 0; 107 | toWait.push(this.each($('style'), async (i, el) => { 108 | let mimeType = $(el).attr('type') || 'text/plain'; 109 | 110 | let thisCtx = Object.assign({ 111 | count: styleCount++, 112 | tag: 'style' 113 | }, compilerContext); 114 | 115 | let origText = $(el).text(); 116 | let newText = await that.compileBlock(origText, filePath, mimeType, thisCtx); 117 | 118 | if (origText !== newText) { 119 | $(el).text(newText); 120 | $(el).attr('type', 'text/css'); 121 | } 122 | })); 123 | 124 | let scriptCount = 0; 125 | toWait.push(this.each($('script'), async (i, el) => { 126 | let src = $(el).attr('src'); 127 | if (src && src.length > 2) { 128 | $(el).attr('src', InlineHtmlCompiler.fixupRelativeUrl(src)); 129 | return; 130 | } 131 | 132 | let thisCtx = Object.assign({ 133 | count: scriptCount++, 134 | tag: 'script' 135 | }, compilerContext); 136 | 137 | let mimeType = $(el).attr('type') || 'application/javascript'; 138 | let origText = $(el).text(); 139 | let newText = await that.compileBlock(origText, filePath, mimeType, thisCtx); 140 | 141 | if (origText !== newText) { 142 | $(el).text(newText); 143 | $(el).attr('type', 'application/javascript'); 144 | } 145 | })); 146 | 147 | $('link').map((i, el) => { 148 | let href = $(el).attr('href'); 149 | if (href && href.length > 2) { $(el).attr('href', InlineHtmlCompiler.fixupRelativeUrl(href)); } 150 | 151 | // NB: In recent versions of Chromium, the link type MUST be text/css or 152 | // it will be flat-out ignored. Also I hate myself for hardcoding these. 153 | let type = $(el).attr('type'); 154 | if (compiledCSS[type]) $(el).attr('type', 'text/css'); 155 | }); 156 | 157 | $('x-require').map((i, el) => { 158 | let src = $(el).attr('src'); 159 | 160 | // File URL? Bail 161 | if (src.match(/^file:/i)) return; 162 | 163 | // Absolute path? Bail. 164 | if (src.match(/^([\/]|[A-Za-z]:)/i)) return; 165 | 166 | try { 167 | $(el).attr('src', path.resolve(path.dirname(filePath), src)); 168 | } catch (e) { 169 | $(el).text(`${e.message}\n${e.stack}`); 170 | } 171 | }); 172 | 173 | await Promise.all(toWait); 174 | 175 | return { 176 | code: $.html(), 177 | mimeType: 'text/html' 178 | }; 179 | } 180 | 181 | shouldCompileFileSync(fileName, compilerContext) { // eslint-disable-line no-unused-vars 182 | return true; 183 | } 184 | 185 | determineDependentFilesSync(sourceCode, filePath, compilerContext) { // eslint-disable-line no-unused-vars 186 | return []; 187 | } 188 | 189 | compileSync(sourceCode, filePath, compilerContext) { 190 | cheerio = cheerio || require('cheerio'); 191 | 192 | //Leave the attributes casing as it is, because of Angular 2 and maybe other case-sensitive frameworks 193 | let $ = cheerio.load(sourceCode, {lowerCaseAttributeNames: false}); 194 | 195 | let that = this; 196 | let styleCount = 0; 197 | this.eachSync($('style'), async (i, el) => { 198 | let mimeType = $(el).attr('type'); 199 | 200 | let thisCtx = Object.assign({ 201 | count: styleCount++, 202 | tag: 'style' 203 | }, compilerContext); 204 | 205 | let origText = $(el).text(); 206 | let newText = that.compileBlockSync(origText, filePath, mimeType, thisCtx); 207 | 208 | if (origText !== newText) { 209 | $(el).text(newText); 210 | $(el).attr('type', 'text/css'); 211 | } 212 | }); 213 | 214 | let scriptCount = 0; 215 | this.eachSync($('script'), async (i, el) => { 216 | let src = $(el).attr('src'); 217 | if (src && src.length > 2) { 218 | $(el).attr('src', InlineHtmlCompiler.fixupRelativeUrl(src)); 219 | return; 220 | } 221 | 222 | let thisCtx = Object.assign({ 223 | count: scriptCount++, 224 | tag: 'script' 225 | }, compilerContext); 226 | 227 | let mimeType = $(el).attr('type'); 228 | 229 | let oldText = $(el).text(); 230 | let newText = that.compileBlockSync(oldText, filePath, mimeType, thisCtx); 231 | 232 | if (oldText !== newText) { 233 | $(el).text(newText); 234 | $(el).attr('type', 'application/javascript'); 235 | } 236 | }); 237 | 238 | $('link').map((i, el) => { 239 | let href = $(el).attr('href'); 240 | if (href && href.length > 2) { $(el).attr('href', InlineHtmlCompiler.fixupRelativeUrl(href)); } 241 | 242 | // NB: In recent versions of Chromium, the link type MUST be text/css or 243 | // it will be flat-out ignored. Also I hate myself for hardcoding these. 244 | let type = $(el).attr('type'); 245 | if (compiledCSS[type]) $(el).attr('type', 'text/css'); 246 | }); 247 | 248 | $('x-require').map((i, el) => { 249 | let src = $(el).attr('src'); 250 | 251 | // File URL? Bail 252 | if (src.match(/^file:/i)) return; 253 | 254 | // Absolute path? Bail. 255 | if (src.match(/^([\/]|[A-Za-z]:)/i)) return; 256 | 257 | try { 258 | $(el).attr('src', path.resolve(path.dirname(filePath), src)); 259 | } catch (e) { 260 | $(el).text(`${e.message}\n${e.stack}`); 261 | } 262 | }); 263 | 264 | return { 265 | code: $.html(), 266 | mimeType: 'text/html' 267 | }; 268 | } 269 | 270 | getCompilerVersion() { 271 | let thisVersion = require('../../package.json').version; 272 | let compilers = this.allCompilers || []; 273 | let otherVersions = compilers.map((x) => x.getCompilerVersion).join(); 274 | 275 | return `${thisVersion},${otherVersions}`; 276 | } 277 | 278 | static fixupRelativeUrl(url) { 279 | if (!url.match(/^\/\//)) return url; 280 | return `https:${url}`; 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /src/html/jade.js: -------------------------------------------------------------------------------- 1 | import {SimpleCompilerBase} from '../compiler-base'; 2 | 3 | const inputMimeTypes = ['text/jade']; 4 | let jade = null; 5 | 6 | /** 7 | * @access private 8 | */ 9 | export default class JadeCompiler extends SimpleCompilerBase { 10 | constructor() { 11 | super(); 12 | this.compilerOptions.sourceMap = true; 13 | } 14 | 15 | static getInputMimeTypes() { 16 | return inputMimeTypes; 17 | } 18 | 19 | compileSync(sourceCode, filePath) { 20 | jade = jade || require('jade'); 21 | 22 | let code = jade.render( 23 | sourceCode, 24 | Object.assign({ filename: filePath, cache: false }, this.compilerOptions)); 25 | 26 | return { code, mimeType: 'text/html' }; 27 | } 28 | 29 | getCompilerVersion() { 30 | return require('jade/package.json').version; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/html/vue.js: -------------------------------------------------------------------------------- 1 | import {CompilerBase} from '../compiler-base'; 2 | import toutSuite from 'toutsuite'; 3 | 4 | const inputMimeTypes = ['text/vue']; 5 | let vueify = null; 6 | 7 | const mimeTypeToSimpleType = { 8 | 'text/coffeescript': 'coffee', 9 | 'text/typescript': 'ts', 10 | 'application/javascript': 'js', 11 | 'text/jade': 'jade', 12 | 'text/less': 'less', 13 | 'text/sass': 'sass', 14 | 'text/scss': 'scss', 15 | 'text/stylus': 'stylus', 16 | }; 17 | 18 | /** 19 | * @access private 20 | */ 21 | export default class VueCompiler extends CompilerBase { 22 | constructor(asyncCompilers, syncCompilers) { 23 | super(); 24 | Object.assign(this, { asyncCompilers, syncCompilers }); 25 | 26 | this.compilerOptions = {}; 27 | } 28 | 29 | static createFromCompilers(compilersByMimeType) { 30 | let makeAsyncCompilers = () => Object.keys(compilersByMimeType).reduce((acc, mimeType) => { 31 | let compiler = compilersByMimeType[mimeType]; 32 | 33 | acc[mimeType] = async (content, cb, vueCompiler, filePath) => { 34 | let ctx = {}; 35 | try { 36 | if (!await compiler.shouldCompileFile(filePath, ctx)) { 37 | cb(null, content); 38 | return; 39 | } 40 | 41 | let result = await compiler.compile(content, filePath, ctx); 42 | cb(null, result.code); 43 | return; 44 | } catch (e) { 45 | cb(e); 46 | } 47 | }; 48 | 49 | let st = mimeTypeToSimpleType[mimeType]; 50 | if (st) acc[st] = acc[mimeType]; 51 | 52 | return acc; 53 | }, {}); 54 | 55 | let makeSyncCompilers = () => Object.keys(compilersByMimeType).reduce((acc, mimeType) => { 56 | let compiler = compilersByMimeType[mimeType]; 57 | 58 | acc[mimeType] = (content, cb, vueCompiler, filePath) => { 59 | let ctx = {}; 60 | try { 61 | if (!compiler.shouldCompileFileSync(filePath, ctx)) { 62 | cb(null, content); 63 | return; 64 | } 65 | 66 | let result = compiler.compileSync(content, filePath, ctx); 67 | cb(null, result.code); 68 | return; 69 | } catch (e) { 70 | cb(e); 71 | } 72 | }; 73 | 74 | let st = mimeTypeToSimpleType[mimeType]; 75 | if (st) acc[st] = acc[mimeType]; 76 | 77 | return acc; 78 | }, {}); 79 | 80 | // NB: This is super hacky but we have to defer building asyncCompilers 81 | // and syncCompilers until compilersByMimeType is filled out 82 | let ret = new VueCompiler(null, null); 83 | 84 | let asyncCompilers, syncCompilers; 85 | Object.defineProperty(ret, 'asyncCompilers', { 86 | get: () => { 87 | asyncCompilers = asyncCompilers || makeAsyncCompilers(); 88 | return asyncCompilers; 89 | } 90 | }); 91 | 92 | Object.defineProperty(ret, 'syncCompilers', { 93 | get: () => { 94 | syncCompilers = syncCompilers || makeSyncCompilers(); 95 | return syncCompilers; 96 | } 97 | }); 98 | 99 | return ret; 100 | } 101 | 102 | static getInputMimeTypes() { 103 | return inputMimeTypes; 104 | } 105 | 106 | async shouldCompileFile(fileName, compilerContext) { // eslint-disable-line no-unused-vars 107 | return true; 108 | } 109 | 110 | async determineDependentFiles(sourceCode, filePath, compilerContext) { // eslint-disable-line no-unused-vars 111 | return []; 112 | } 113 | 114 | async compile(sourceCode, filePath, compilerContext) { // eslint-disable-line no-unused-vars 115 | vueify = vueify || require('@paulcbetts/vueify'); 116 | 117 | let opts = Object.assign({}, this.compilerOptions); 118 | 119 | let code = await new Promise((res, rej) => { 120 | vueify.compiler.compileNoGlobals(sourceCode, filePath, this.asyncCompilers, opts, (e,r) => { 121 | if (e) { rej(e); } else { res(r); } 122 | }); 123 | }); 124 | 125 | return { 126 | code, 127 | mimeType: 'application/javascript' 128 | }; 129 | } 130 | 131 | shouldCompileFileSync(fileName, compilerContext) { // eslint-disable-line no-unused-vars 132 | return true; 133 | } 134 | 135 | determineDependentFilesSync(sourceCode, filePath, compilerContext) { // eslint-disable-line no-unused-vars 136 | return []; 137 | } 138 | 139 | compileSync(sourceCode, filePath, compilerContext) { // eslint-disable-line no-unused-vars 140 | vueify = vueify || require('@paulcbetts/vueify'); 141 | 142 | let opts = Object.assign({}, this.compilerOptions); 143 | 144 | let err,code; 145 | toutSuite(() => { 146 | vueify.compiler.compileNoGlobals(sourceCode, filePath, this.syncCompilers, opts, (e,r) => { 147 | if (e) { err = e; } else { code = r; } 148 | }); 149 | }); 150 | 151 | if (err) throw err; 152 | 153 | return { 154 | code, 155 | mimeType: 'application/javascript' 156 | }; 157 | } 158 | 159 | getCompilerVersion() { 160 | // NB: See same issue with SASS and user-scoped modules as to why we hard-code this 161 | let thisVersion = '9.4.0'; 162 | let compilers = this.allCompilers || []; 163 | let otherVersions = compilers.map((x) => x.getCompilerVersion).join(); 164 | 165 | return `${thisVersion},${otherVersions}`; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/js/babel.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import {SimpleCompilerBase} from '../compiler-base'; 3 | 4 | const mimeTypes = ['text/jsx', 'application/javascript']; 5 | let babel = null; 6 | let istanbul = null; 7 | 8 | export default class BabelCompiler extends SimpleCompilerBase { 9 | constructor() { 10 | super(); 11 | } 12 | 13 | static getInputMimeTypes() { 14 | return mimeTypes; 15 | } 16 | 17 | // NB: This method exists to stop Babel from trying to load plugins from the 18 | // app's node_modules directory, which in a production app doesn't have Babel 19 | // installed in it. Instead, we try to load from our entry point's node_modules 20 | // directory (i.e. Grunt perhaps), and if it doesn't work, just keep going. 21 | attemptToPreload(names, prefix) { 22 | if (!names.length) return null; 23 | 24 | const fixupModule = (exp) => { 25 | // NB: Some plugins like transform-decorators-legacy, use import/export 26 | // semantics, and others don't 27 | if ('default' in exp) return exp['default']; 28 | return exp; 29 | }; 30 | 31 | const nodeModulesAboveUs = path.resolve(__dirname, '..', '..', '..'); 32 | 33 | const preloadStrategies = [ 34 | x => fixupModule(require.main.require(x)), 35 | x => fixupModule(require(path.join(nodeModulesAboveUs, x))), 36 | x => fixupModule(require(x)) 37 | ]; 38 | 39 | const possibleNames = (name) => { 40 | let names = [`babel-${prefix}-${name}`]; 41 | 42 | if (prefix === 'plugin') { 43 | // Look for module names that do not start with "babel-plugin-" 44 | names.push(name); 45 | } 46 | 47 | return names; 48 | }; 49 | 50 | // Apply one preloading strategy to the possible names of a module, and return the preloaded 51 | // module if found, null otherwise 52 | const preloadPossibleNames = (name, strategy) => { 53 | if (typeof strategy !== 'function') return null; 54 | 55 | return possibleNames(name).reduce((mod, possibleName)=>{ 56 | if (mod !== null) return mod; 57 | 58 | try { 59 | return strategy(possibleName); 60 | } catch(e) {} // eslint-disable-line no-empty 61 | 62 | return null; 63 | }, null); 64 | }; 65 | 66 | // Pick a loading strategy that finds the first plugin, the same strategy will be 67 | // used to preload all plugins 68 | const selectedStrategy = preloadStrategies.reduce((winner, strategy)=>{ 69 | if (winner !== null) return winner; 70 | return preloadPossibleNames(names[0], strategy) === null ? null : strategy; 71 | }, null); 72 | 73 | return names.map(name => preloadPossibleNames(name, selectedStrategy)).filter((mod) => mod !== null); 74 | } 75 | 76 | compileSync(sourceCode, filePath, compilerContext) { // eslint-disable-line no-unused-vars 77 | babel = babel || require('babel-core'); 78 | 79 | let opts = Object.assign({}, this.compilerOptions, { 80 | filename: filePath, 81 | ast: false, 82 | babelrc: false 83 | }); 84 | 85 | let useCoverage = false; 86 | if ('coverage' in opts) { 87 | useCoverage = !!opts.coverage; 88 | delete opts.coverage; 89 | } 90 | 91 | if ('plugins' in opts) { 92 | let plugins = this.attemptToPreload(opts.plugins, 'plugin'); 93 | if (plugins && plugins.length === opts.plugins.length) opts.plugins = plugins; 94 | } 95 | 96 | if ('presets' in opts) { 97 | let presets = this.attemptToPreload(opts.presets, 'preset'); 98 | if (presets && presets.length === opts.presets.length) opts.presets = presets; 99 | } 100 | 101 | const output = babel.transform(sourceCode, opts); 102 | let sourceMaps = output.map ? JSON.stringify(output.map) : null; 103 | 104 | let code = output.code; 105 | if (useCoverage) { 106 | istanbul = istanbul || require('istanbul'); 107 | 108 | sourceMaps = null; 109 | code = (new istanbul.Instrumenter()).instrumentSync(output.code, filePath); 110 | } 111 | 112 | return { code, sourceMaps, mimeType: 'application/javascript', }; 113 | } 114 | 115 | getCompilerVersion() { 116 | return require('babel-core/package.json').version; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/js/coffeescript.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import btoa from 'btoa'; 3 | import {SimpleCompilerBase} from '../compiler-base'; 4 | 5 | const inputMimeTypes = ['text/coffeescript']; 6 | let coffee = null; 7 | 8 | /** 9 | * @access private 10 | */ 11 | export default class CoffeeScriptCompiler extends SimpleCompilerBase { 12 | constructor() { 13 | super(); 14 | this.compilerOptions.sourceMap = true; 15 | } 16 | 17 | static getInputMimeTypes() { 18 | return inputMimeTypes; 19 | } 20 | 21 | compileSync(sourceCode, filePath) { 22 | coffee = coffee || require('coffee-script'); 23 | 24 | let {js, v3SourceMap} = coffee.compile( 25 | sourceCode, 26 | Object.assign({ filename: filePath }, this.compilerOptions)); 27 | 28 | js = `${js}\n` + 29 | `//# sourceMappingURL=data:application/json;base64,${btoa(unescape(encodeURIComponent(v3SourceMap)))}\n` + 30 | `//# sourceURL=${this.convertFilePath(filePath)}`; 31 | 32 | return { 33 | code: js, 34 | mimeType: 'application/javascript' 35 | }; 36 | } 37 | 38 | convertFilePath(filePath) { 39 | if (process.platform === 'win32') { 40 | filePath = `/${path.resolve(filePath).replace(/\\/g, '/')}`; 41 | } 42 | 43 | return encodeURI(filePath); 44 | } 45 | 46 | getCompilerVersion() { 47 | return require('coffee-script/package.json').version; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/js/graphql.js: -------------------------------------------------------------------------------- 1 | import {SimpleCompilerBase} from '../compiler-base'; 2 | 3 | const inputMimeTypes = ['text/graphql']; 4 | let gql = null; 5 | 6 | export default class GraphQLCompiler extends SimpleCompilerBase { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | static getInputMimeTypes() { 12 | return inputMimeTypes; 13 | } 14 | 15 | compileSync(sourceCode) { 16 | gql = gql || require('graphql-tag/loader'); 17 | // This is a stub, original loader calls webpack-specific this.cacheable() 18 | const webpackLoaderStub = { 19 | cacheable: () => {} 20 | }; 21 | const loader = gql.bind(webpackLoaderStub); 22 | 23 | const js = loader(sourceCode); 24 | 25 | return { 26 | code: js, 27 | mimeType: 'application/javascript' 28 | }; 29 | } 30 | 31 | getCompilerVersion() { 32 | return require('graphql-tag/package.json').version; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/js/typescript.js: -------------------------------------------------------------------------------- 1 | import {SimpleCompilerBase} from '../compiler-base'; 2 | import path from 'path'; 3 | import jsEscape from 'js-string-escape'; 4 | 5 | const inputMimeTypes = ['text/typescript', 'text/tsx']; 6 | const d = require('debug')('electron-compile:typescript-compiler'); 7 | 8 | let ts = null; 9 | let istanbul = null; 10 | let sorcery = null; 11 | 12 | const builtinKeys = ['hotModuleReload', 'coverage', 'babel']; 13 | 14 | export default class TypeScriptCompiler extends SimpleCompilerBase { 15 | constructor() { 16 | super(); 17 | 18 | this.outMimeType = 'application/javascript'; 19 | this.compilerOptions = { 20 | inlineSourceMap: true, 21 | inlineSources: true 22 | }; 23 | } 24 | 25 | static getInputMimeTypes() { 26 | return inputMimeTypes; 27 | } 28 | 29 | _getParsedConfigOptions(tsCompiler) { 30 | let parsedConfig = this.parsedConfig; 31 | 32 | if (!parsedConfig) { 33 | let opts = Object.assign({}, this.compilerOptions); 34 | let builtinOpts = {}; 35 | builtinKeys.forEach((k) => { 36 | if (k in this.compilerOptions) { 37 | delete opts[k]; 38 | builtinOpts[k] = this.compilerOptions[k]; 39 | } 40 | }); 41 | 42 | const results = tsCompiler.convertCompilerOptionsFromJson(opts); 43 | 44 | if (results.errors && results.errors.length) { 45 | throw new Error(JSON.stringify(results.errors)); 46 | } 47 | 48 | parsedConfig = this.parsedConfig = { typescriptOpts: results.options, builtinOpts }; 49 | } 50 | 51 | return parsedConfig; 52 | } 53 | 54 | compileSync(sourceCode, filePath) { 55 | ts = ts || require('typescript'); 56 | const options = this._getParsedConfigOptions(ts); 57 | 58 | const isTsx = filePath.match(/\.tsx$/i); 59 | const transpileOptions = { 60 | compilerOptions: options.typescriptOpts, 61 | fileName: filePath.match(/\.(ts|tsx)$/i) ? path.basename(filePath) : null 62 | }; 63 | 64 | if (isTsx && options.builtinOpts.hotModuleReload !== false) { 65 | sourceCode = this.addHotModuleLoadingRegistration(sourceCode, filePath, this.getExportsForFile(sourceCode, filePath)); 66 | } 67 | 68 | let output = ts.transpileModule(sourceCode, transpileOptions); 69 | let sourceMaps = output.sourceMapText ? output.sourceMapText : null; 70 | if (options.builtinOpts.coverage) { 71 | sourceMaps = null; 72 | istanbul = istanbul || require('istanbul'); 73 | 74 | sourceMaps = null; 75 | output.outputText = (new istanbul.Instrumenter()).instrumentSync(output.outputText, filePath); 76 | } 77 | 78 | d(JSON.stringify(output.diagnostics)); 79 | 80 | const babelOpts = this.parsedConfig.builtinOpts.babel; 81 | if (babelOpts) { 82 | if (!this.babel) { 83 | const BabelCompiler = require("./babel").default; 84 | this.babel = new BabelCompiler(); 85 | this.babel.compilerOptions = babelOpts; 86 | } 87 | 88 | sorcery = sorcery || require('sorcery'); 89 | 90 | let tsOutputPath = filePath.replace(/.tsx?$/i, ".js"); 91 | let babelOutputPath = filePath.replace(/.tsx?$/i, ".babel.js"); 92 | 93 | output.outputText = output.outputText.replace(/\/\/# sourceMap.*/g, ""); 94 | 95 | let babelOutput = this.babel.compileSync(output.outputText, tsOutputPath); 96 | let chain = sorcery.loadSync(babelOutputPath, { 97 | content: { 98 | [filePath]: sourceCode, 99 | [tsOutputPath]: output.outputText, 100 | [babelOutputPath]: babelOutput.code, 101 | }, 102 | sourcemaps: { 103 | [tsOutputPath]: JSON.parse(sourceMaps), 104 | [babelOutputPath]: JSON.parse(babelOutput.sourceMaps), 105 | } 106 | }); 107 | let finalSourceMaps = chain.apply(); 108 | let outputCode = babelOutput.code + "\n//# sourceMappingURL=" + finalSourceMaps.toUrl(); 109 | 110 | // the only way to make sourceMaps usable seems to be to have 111 | // them inlined right now, see https://github.com/electron/electron-compile/issues/172#issuecomment-277146112 112 | return { 113 | code: outputCode, 114 | mimeType: babelOutput.mimeType, 115 | }; 116 | } 117 | 118 | return { 119 | code: output.outputText, 120 | mimeType: this.outMimeType, 121 | sourceMaps 122 | }; 123 | } 124 | 125 | addHotModuleLoadingRegistration(sourceCode, fileName, exports) { 126 | if (exports.length < 1) return sourceCode; 127 | 128 | let registrations = exports.map(x => { 129 | let id = `${x}` == 'default' ? '(typeof _default !== \'undefined\' ? _default : exports.default)' : `${x}`; 130 | let name = `"${x}"`; 131 | return `__REACT_HOT_LOADER__.register(${id}, ${name}, __FILENAME__);\n`; 132 | }); 133 | 134 | let tmpl = ` 135 | ${sourceCode} 136 | 137 | if (typeof __REACT_HOT_LOADER__ !== 'undefined') { 138 | const __FILENAME__ = "${jsEscape(fileName)}"; 139 | ${registrations} 140 | }`; 141 | 142 | return tmpl; 143 | } 144 | 145 | getExportsForFile(sourceCode, fileName) { 146 | let sourceFile = ts.createSourceFile(fileName, sourceCode, ts.ScriptTarget.ES6); 147 | let ret = []; 148 | 149 | // Walk the tree to search for classes 150 | let visit = (node) => { 151 | if (!this.isNodeExported(node)) return; 152 | 153 | if (node.kind === ts.SyntaxKind.ClassDeclaration || node.kind === ts.SyntaxKind.FunctionDeclaration) { 154 | ret.push(node.name.text); 155 | } 156 | }; 157 | 158 | ts.forEachChild(sourceFile, visit); 159 | 160 | return ret; 161 | } 162 | 163 | isNodeExported(node) { 164 | return (node.flags & ts.NodeFlags.Export) !== 0 || (node.parent && node.parent.kind === ts.SyntaxKind.SourceFile); 165 | } 166 | 167 | getCompilerVersion() { 168 | return require('typescript/package.json').version; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/json/cson.js: -------------------------------------------------------------------------------- 1 | import {SimpleCompilerBase} from '../compiler-base'; 2 | 3 | const inputMimeTypes = ['text/cson']; 4 | let CSON = null; 5 | 6 | /** 7 | * @access private 8 | */ 9 | export default class CSONCompiler extends SimpleCompilerBase { 10 | constructor() { 11 | super(); 12 | } 13 | 14 | static getInputMimeTypes() { 15 | return inputMimeTypes; 16 | } 17 | 18 | compileSync(sourceCode, filePath) { // eslint-disable-line no-unused-vars 19 | CSON = CSON || require('cson'); 20 | 21 | let result = CSON.parse(sourceCode); 22 | 23 | return { 24 | code: JSON.stringify(result), 25 | mimeType: 'application/json' 26 | }; 27 | } 28 | 29 | getCompilerVersion() { 30 | return require('cson/package.json').version; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | const filenames = [ 2 | 'css/less', 3 | 'css/sass', 4 | 'css/stylus', 5 | 'js/babel', 6 | 'js/graphql', 7 | 'js/coffeescript', 8 | 'js/typescript', 9 | 'json/cson', 10 | 'html/inline-html', 11 | 'html/jade', 12 | 'html/vue', 13 | 'passthrough' 14 | ]; 15 | 16 | module.exports = filenames.map((x) => require('./' + x).default); 17 | -------------------------------------------------------------------------------- /src/passthrough.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import {SimpleCompilerBase} from './compiler-base'; 3 | import mimeTypes from '@paulcbetts/mime-types'; 4 | 5 | const inputMimeTypes = ['text/plain', 'image/svg+xml']; 6 | 7 | 8 | /** 9 | * @access private 10 | * 11 | * This class is used for binary files and other files that should end up in 12 | * your cache directory, but aren't actually compiled 13 | */ 14 | export default class PassthroughCompiler extends SimpleCompilerBase { 15 | constructor() { 16 | super(); 17 | } 18 | 19 | static getInputMimeTypes() { 20 | return inputMimeTypes; 21 | } 22 | 23 | compileSync(sourceCode, filePath) { 24 | return { 25 | code: sourceCode, 26 | mimeType: mimeTypes.lookup(filePath) 27 | }; 28 | } 29 | 30 | getCompilerVersion() { 31 | return require(path.join(__dirname, '..', 'package.json')).version; 32 | } 33 | } 34 | --------------------------------------------------------------------------------