Skip to content

Commit 4dcde4b

Browse files
authored
Merge pull request #1389 from sveltejs/gh-1360
integrate CLI
2 parents d010aff + c9494d9 commit 4dcde4b

File tree

57 files changed

+2881
-15
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+2881
-15
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
.DS_Store
22
.nyc_output
33
node_modules
4+
/cli/
45
/compiler/
56
/ssr/
67
/shared.js
78
/scratch/
89
/coverage/
910
/coverage.lcov/
11+
/test/cli/samples/*/actual
1012
/test/sourcemaps/samples/*/output.js
1113
/test/sourcemaps/samples/*/output.js.map
1214
/src/compile/shared.ts

package.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@
33
"version": "2.3.0",
44
"description": "The magical disappearing UI framework",
55
"main": "compiler/svelte.js",
6+
"bin": {
7+
"svelte": "svelte"
8+
},
69
"files": [
10+
"cli",
711
"compiler",
812
"ssr",
913
"shared.js",
1014
"store.js",
1115
"store.umd.js",
16+
"svelte",
1217
"README.md"
1318
],
1419
"scripts": {
@@ -48,14 +53,14 @@
4853
"acorn": "^5.4.1",
4954
"acorn-dynamic-import": "^3.0.0",
5055
"chalk": "^2.4.0",
56+
"clorox": "^1.0.3",
5157
"codecov": "^3.0.0",
5258
"console-group": "^0.3.2",
5359
"css-tree": "1.0.0-alpha22",
5460
"eslint": "^4.19.1",
5561
"eslint-plugin-html": "^4.0.3",
5662
"eslint-plugin-import": "^2.11.0",
5763
"estree-walker": "^0.5.1",
58-
"glob": "^7.1.1",
5964
"is-reference": "^1.1.0",
6065
"jsdom": "^11.8.0",
6166
"locate-character": "^2.0.5",
@@ -75,8 +80,11 @@
7580
"rollup-plugin-typescript": "^0.8.1",
7681
"rollup-plugin-virtual": "^1.0.1",
7782
"rollup-watch": "^4.3.1",
83+
"sade": "^1.4.0",
84+
"sander": "^0.6.0",
7885
"source-map": "0.6",
7986
"source-map-support": "^0.5.4",
87+
"tiny-glob": "^0.2.0",
8088
"ts-node": "^6.0.0",
8189
"tslib": "^1.8.0",
8290
"typescript": "^2.8.3"

rollup.config.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,29 @@ export default [
5757
}
5858
},
5959

60+
/* cli/*.js */
61+
{
62+
input: ['src/cli/index.ts'],
63+
output: {
64+
dir: 'cli',
65+
format: 'cjs'
66+
},
67+
external: ['fs', 'path', 'os', 'svelte'],
68+
paths: {
69+
svelte: '../compiler/svelte.js'
70+
},
71+
plugins: [
72+
json(),
73+
commonjs(),
74+
resolve(),
75+
typescript({
76+
typescript: require('typescript')
77+
})
78+
],
79+
experimentalDynamicImport: true,
80+
experimentalCodeSplitting: true
81+
},
82+
6083
/* shared.js */
6184
{
6285
input: 'src/shared/index.js',

src/cli/compile.ts

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import * as path from 'path';
2+
import * as fs from 'fs';
3+
import * as svelte from 'svelte';
4+
import error from './error.js';
5+
6+
function mkdirp(dir) {
7+
const parent = path.dirname(dir);
8+
if (dir === parent) return;
9+
10+
mkdirp(parent);
11+
if (!fs.existsSync(dir)) fs.mkdirSync(dir);
12+
}
13+
14+
export function compile(input, opts) {
15+
if (opts._.length > 0) {
16+
error(`Can only compile a single file or directory`);
17+
}
18+
19+
const output = opts.output;
20+
21+
const stats = fs.statSync(input);
22+
const isDir = stats.isDirectory();
23+
24+
if (isDir) {
25+
if (!output) {
26+
error(`You must specify an --output (-o) option when compiling a directory of files`);
27+
}
28+
29+
if (opts.name || opts.amdId) {
30+
error(`Cannot specify --${opts.name ? 'name' : 'amdId'} when compiling a directory`);
31+
}
32+
}
33+
34+
const globals = {};
35+
if (opts.globals) {
36+
opts.globals.split(',').forEach(pair => {
37+
const [key, value] = pair.split(':');
38+
globals[key] = value;
39+
});
40+
}
41+
42+
const options = {
43+
name: opts.name,
44+
format: opts.format,
45+
sourceMap: opts.sourcemap,
46+
globals,
47+
css: opts.css !== false,
48+
dev: opts.dev,
49+
immutable: opts.immutable,
50+
generate: opts.generate || 'dom',
51+
customElement: opts.customElement,
52+
store: opts.store
53+
};
54+
55+
if (isDir) {
56+
mkdirp(output);
57+
compileDirectory(input, output, options);
58+
} else {
59+
compileFile(input, output, options);
60+
}
61+
}
62+
63+
function compileDirectory(input, output, options) {
64+
fs.readdirSync(input).forEach(file => {
65+
const src = path.resolve(input, file);
66+
const dest = path.resolve(output, file);
67+
68+
if (path.extname(file) === '.html') {
69+
compileFile(
70+
src,
71+
dest.substring(0, dest.lastIndexOf('.html')) + '.js',
72+
options
73+
);
74+
} else {
75+
const stats = fs.statSync(src);
76+
if (stats.isDirectory()) {
77+
compileDirectory(src, dest, options);
78+
}
79+
}
80+
});
81+
}
82+
83+
let SOURCEMAPPING_URL = 'sourceMa';
84+
SOURCEMAPPING_URL += 'ppingURL';
85+
86+
function compileFile(input, output, options) {
87+
console.error(`compiling ${path.relative(process.cwd(), input)}...`); // eslint-disable-line no-console
88+
89+
options = Object.assign({}, options);
90+
if (!options.name) options.name = getName(input);
91+
92+
options.filename = input;
93+
options.outputFilename = output;
94+
95+
const { sourceMap } = options;
96+
const inline = sourceMap === 'inline';
97+
98+
let source = fs.readFileSync(input, 'utf-8');
99+
if (source[0] === 0xfeff) source = source.slice(1);
100+
101+
let compiled;
102+
103+
try {
104+
compiled = svelte.compile(source, options);
105+
} catch (err) {
106+
error(err);
107+
}
108+
109+
const { js } = compiled;
110+
111+
if (sourceMap) {
112+
js.code += `\n//# ${SOURCEMAPPING_URL}=${inline || !output
113+
? js.map.toUrl()
114+
: `${path.basename(output)}.map`}\n`;
115+
}
116+
117+
if (output) {
118+
const outputDir = path.dirname(output);
119+
mkdirp(outputDir);
120+
fs.writeFileSync(output, js.code);
121+
console.error(`wrote ${path.relative(process.cwd(), output)}`); // eslint-disable-line no-console
122+
if (sourceMap && !inline) {
123+
fs.writeFileSync(`${output}.map`, js.map);
124+
console.error(`wrote ${path.relative(process.cwd(), `${output}.map`)}`); // eslint-disable-line no-console
125+
}
126+
} else {
127+
process.stdout.write(js.code);
128+
}
129+
}
130+
131+
function getName(input) {
132+
return path
133+
.basename(input)
134+
.replace(path.extname(input), '')
135+
.replace(/[^a-zA-Z_$0-9]+/g, '_')
136+
.replace(/^_/, '')
137+
.replace(/_$/, '')
138+
.replace(/^(\d)/, '_$1');
139+
}

src/cli/error.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import clorox from 'clorox';
2+
3+
function stderr(msg) {
4+
console.error(msg); // eslint-disable-line no-console
5+
}
6+
7+
export default function error(err) {
8+
stderr(`${clorox.red(err.message || err)}`);
9+
10+
if (err.frame) {
11+
stderr(err.frame); // eslint-disable-line no-console
12+
} else if (err.stack) {
13+
stderr(`${clorox.grey(err.stack)}`);
14+
}
15+
16+
process.exit(1);
17+
}

src/cli/index.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import sade from 'sade';
2+
import * as pkg from '../../package.json';
3+
4+
const prog = sade('svelte-cli').version(pkg.version);
5+
6+
prog
7+
.command('compile <input>')
8+
9+
.option('-o, --output', 'Output (if absent, prints to stdout)')
10+
.option('-f, --format', 'Type of output (amd, cjs, es, iife, umd)')
11+
.option('-g, --globals', 'Comma-separate list of `module ID:Global` pairs')
12+
.option('-n, --name', 'Name for IIFE/UMD export (inferred from filename by default)')
13+
.option('-m, --sourcemap', 'Generate sourcemap (`-m inline` for inline map)')
14+
.option('-d, --dev', 'Add dev mode warnings and errors')
15+
.option('--amdId', 'ID for AMD module (default is anonymous)')
16+
.option('--generate', 'Change generate format between `dom` and `ssr`')
17+
.option('--no-css', `Don't include CSS (useful with SSR)`)
18+
.option('--immutable', 'Support immutable data structures')
19+
20+
.example('compile App.html > App.js')
21+
.example('compile src -o dest')
22+
.example('compile -f umd MyComponent.html > MyComponent.js')
23+
24+
.action((input, opts) => {
25+
import('./compile.js').then(({ compile }) => {
26+
compile(input, opts);
27+
});
28+
})
29+
30+
.parse(process.argv);

svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env node
2+
require('./cli/index.ts.js');

test/cli/index.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const child_process = require('child_process');
4+
const assert = require('assert');
5+
const glob = require('tiny-glob/sync.js');
6+
7+
const bin = path.resolve(`svelte`);
8+
9+
function normalize(str) {
10+
return str
11+
.replace(/^\s+$/gm, '')
12+
.replace(/generated by Svelte v[.\d]+/, `generated by Svelte vx.y.z`)
13+
.trim();
14+
}
15+
16+
const cwd = process.cwd();
17+
18+
describe('svelte-cli', () => {
19+
afterEach(() => {
20+
process.chdir(cwd);
21+
});
22+
23+
fs.readdirSync('test/cli/samples').forEach(dir => {
24+
if (dir[0] === '.') return;
25+
26+
// append .solo to test dir to only run that test
27+
const solo = /\.solo$/.test(dir);
28+
29+
(solo ? it.only : it)(dir, done => {
30+
process.chdir(`${__dirname}/samples/${dir}`);
31+
32+
const command = fs.readFileSync('command.sh', 'utf-8');
33+
34+
child_process.exec(`
35+
alias svelte=${bin}
36+
mkdir -p actual
37+
rm -rf actual/*
38+
${command}
39+
`, (err, stdout, stderr) => {
40+
if (err) {
41+
done(err);
42+
return;
43+
}
44+
45+
const actual = glob('**', { cwd: 'actual', filesOnly: true })
46+
.map(file => {
47+
return {
48+
file,
49+
contents: normalize(fs.readFileSync(`actual/${file}`, 'utf-8'))
50+
};
51+
});
52+
53+
const expected = glob('**', { cwd: 'expected', filesOnly: true })
54+
.map(file => {
55+
return {
56+
file,
57+
contents: normalize(
58+
fs.readFileSync(`expected/${file}`, 'utf-8')
59+
)
60+
};
61+
});
62+
63+
console.log(actual);
64+
console.log(expected);
65+
66+
actual.forEach((a, i) => {
67+
const e = expected[i];
68+
69+
assert.equal(a.file, e.file, 'File list mismatch');
70+
71+
if (/\.map$/.test(a.file)) {
72+
assert.deepEqual(JSON.parse(a.contents), JSON.parse(e.contents));
73+
} else {
74+
assert.equal(a.contents, e.contents);
75+
}
76+
});
77+
78+
done();
79+
});
80+
});
81+
});
82+
});

test/cli/samples/basic/command.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
svelte compile src/Main.html > actual/Main.js

0 commit comments

Comments
 (0)