UNPKG

3.36 kBJavaScriptView Raw
1const Asset = require('../Asset');
2const commandExists = require('command-exists');
3const localRequire = require('../utils/localRequire');
4const {minify} = require('terser');
5const path = require('path');
6const spawn = require('cross-spawn');
7
8class ElmAsset extends Asset {
9 constructor(name, options) {
10 super(name, options);
11 this.type = 'js';
12 }
13
14 async parse() {
15 let options = {
16 cwd: path.dirname(this.name)
17 };
18
19 // If elm is not installed globally, install it locally.
20 try {
21 await commandExists('elm');
22 } catch (err) {
23 await localRequire('elm', this.name);
24 options.pathToElm = path.join(
25 path.dirname(require.resolve('elm')),
26 'bin',
27 'elm'
28 );
29 }
30
31 this.elm = await localRequire('node-elm-compiler', this.name);
32
33 // Ensure that an elm.json file exists, and initialize one if not.
34 let elmConfig = await this.getConfig(['elm.json'], {load: false});
35 if (!elmConfig) {
36 await this.createElmConfig(options);
37
38 // Ensure we are watching elm.json for changes
39 await this.getConfig(['elm.json'], {load: false});
40 }
41
42 options.debug = !this.options.production;
43 if (this.options.minify) {
44 options.optimize = true;
45 }
46
47 this.elmOpts = options;
48 }
49
50 async collectDependencies() {
51 let dependencies = await this.elm.findAllDependencies(this.name);
52 for (let dependency of dependencies) {
53 this.addDependency(dependency, {includedInParent: true});
54 }
55 }
56
57 async createElmConfig(options) {
58 let cp = spawn(options.pathToElm || 'elm', ['init']);
59 cp.stdin.write('y\n');
60
61 return new Promise((resolve, reject) => {
62 cp.on('error', reject);
63 cp.on('close', function(code) {
64 if (code !== 0) {
65 return reject(new Error('elm init failed.'));
66 }
67
68 return resolve();
69 });
70 });
71 }
72
73 async generate() {
74 let compiled = await this.elm.compileToString(this.name, this.elmOpts);
75 this.contents = compiled.toString();
76 if (this.options.hmr) {
77 let {inject} = await localRequire('elm-hot', this.name);
78 this.contents = inject(this.contents);
79 }
80
81 let output = this.contents;
82
83 if (this.options.minify) {
84 output = pack(output);
85 }
86
87 return {
88 [this.type]: output
89 };
90
91 // Recommended minification
92 // Based on:
93 // - http://elm-lang.org/0.19.0/optimize
94 function pack(source) {
95 let options = {
96 compress: {
97 keep_fargs: false,
98 passes: 2,
99 pure_funcs: [
100 'F2',
101 'F3',
102 'F4',
103 'F5',
104 'F6',
105 'F7',
106 'F8',
107 'F9',
108 'A2',
109 'A3',
110 'A4',
111 'A5',
112 'A6',
113 'A7',
114 'A8',
115 'A9'
116 ],
117 pure_getters: true,
118 unsafe: true,
119 unsafe_comps: true
120 },
121 mangle: true,
122 rename: false
123 };
124
125 let result = minify(source, options);
126
127 if (result.error) {
128 throw result.error;
129 }
130
131 return result.code;
132 }
133 }
134
135 generateErrorMessage(err) {
136 // The generated stack is not useful, but other code may
137 // expect it and try to print it, so make it an empty string.
138 err.stack = '';
139 return err;
140 }
141}
142
143module.exports = ElmAsset;