Skip to content

Commit

Permalink
Merge pull request #1389 from sveltejs/gh-1360
Browse files Browse the repository at this point in the history
integrate CLI
  • Loading branch information
Rich-Harris authored Apr 30, 2018
2 parents d010aff + c9494d9 commit 4dcde4b
Show file tree
Hide file tree
Showing 57 changed files with 2,881 additions and 15 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
.DS_Store
.nyc_output
node_modules
/cli/
/compiler/
/ssr/
/shared.js
/scratch/
/coverage/
/coverage.lcov/
/test/cli/samples/*/actual
/test/sourcemaps/samples/*/output.js
/test/sourcemaps/samples/*/output.js.map
/src/compile/shared.ts
Expand Down
10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
"version": "2.3.0",
"description": "The magical disappearing UI framework",
"main": "compiler/svelte.js",
"bin": {
"svelte": "svelte"
},
"files": [
"cli",
"compiler",
"ssr",
"shared.js",
"store.js",
"store.umd.js",
"svelte",
"README.md"
],
"scripts": {
Expand Down Expand Up @@ -48,14 +53,14 @@
"acorn": "^5.4.1",
"acorn-dynamic-import": "^3.0.0",
"chalk": "^2.4.0",
"clorox": "^1.0.3",
"codecov": "^3.0.0",
"console-group": "^0.3.2",
"css-tree": "1.0.0-alpha22",
"eslint": "^4.19.1",
"eslint-plugin-html": "^4.0.3",
"eslint-plugin-import": "^2.11.0",
"estree-walker": "^0.5.1",
"glob": "^7.1.1",
"is-reference": "^1.1.0",
"jsdom": "^11.8.0",
"locate-character": "^2.0.5",
Expand All @@ -75,8 +80,11 @@
"rollup-plugin-typescript": "^0.8.1",
"rollup-plugin-virtual": "^1.0.1",
"rollup-watch": "^4.3.1",
"sade": "^1.4.0",
"sander": "^0.6.0",
"source-map": "0.6",
"source-map-support": "^0.5.4",
"tiny-glob": "^0.2.0",
"ts-node": "^6.0.0",
"tslib": "^1.8.0",
"typescript": "^2.8.3"
Expand Down
23 changes: 23 additions & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,29 @@ export default [
}
},

/* cli/*.js */
{
input: ['src/cli/index.ts'],
output: {
dir: 'cli',
format: 'cjs'
},
external: ['fs', 'path', 'os', 'svelte'],
paths: {
svelte: '../compiler/svelte.js'
},
plugins: [
json(),
commonjs(),
resolve(),
typescript({
typescript: require('typescript')
})
],
experimentalDynamicImport: true,
experimentalCodeSplitting: true
},

/* shared.js */
{
input: 'src/shared/index.js',
Expand Down
139 changes: 139 additions & 0 deletions src/cli/compile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import * as path from 'path';
import * as fs from 'fs';
import * as svelte from 'svelte';
import error from './error.js';

function mkdirp(dir) {
const parent = path.dirname(dir);
if (dir === parent) return;

mkdirp(parent);
if (!fs.existsSync(dir)) fs.mkdirSync(dir);
}

export function compile(input, opts) {
if (opts._.length > 0) {
error(`Can only compile a single file or directory`);
}

const output = opts.output;

const stats = fs.statSync(input);
const isDir = stats.isDirectory();

if (isDir) {
if (!output) {
error(`You must specify an --output (-o) option when compiling a directory of files`);
}

if (opts.name || opts.amdId) {
error(`Cannot specify --${opts.name ? 'name' : 'amdId'} when compiling a directory`);
}
}

const globals = {};
if (opts.globals) {
opts.globals.split(',').forEach(pair => {
const [key, value] = pair.split(':');
globals[key] = value;
});
}

const options = {
name: opts.name,
format: opts.format,
sourceMap: opts.sourcemap,
globals,
css: opts.css !== false,
dev: opts.dev,
immutable: opts.immutable,
generate: opts.generate || 'dom',
customElement: opts.customElement,
store: opts.store
};

if (isDir) {
mkdirp(output);
compileDirectory(input, output, options);
} else {
compileFile(input, output, options);
}
}

function compileDirectory(input, output, options) {
fs.readdirSync(input).forEach(file => {
const src = path.resolve(input, file);
const dest = path.resolve(output, file);

if (path.extname(file) === '.html') {
compileFile(
src,
dest.substring(0, dest.lastIndexOf('.html')) + '.js',
options
);
} else {
const stats = fs.statSync(src);
if (stats.isDirectory()) {
compileDirectory(src, dest, options);
}
}
});
}

let SOURCEMAPPING_URL = 'sourceMa';
SOURCEMAPPING_URL += 'ppingURL';

function compileFile(input, output, options) {
console.error(`compiling ${path.relative(process.cwd(), input)}...`); // eslint-disable-line no-console

options = Object.assign({}, options);
if (!options.name) options.name = getName(input);

options.filename = input;
options.outputFilename = output;

const { sourceMap } = options;
const inline = sourceMap === 'inline';

let source = fs.readFileSync(input, 'utf-8');
if (source[0] === 0xfeff) source = source.slice(1);

let compiled;

try {
compiled = svelte.compile(source, options);
} catch (err) {
error(err);
}

const { js } = compiled;

if (sourceMap) {
js.code += `\n//# ${SOURCEMAPPING_URL}=${inline || !output
? js.map.toUrl()
: `${path.basename(output)}.map`}\n`;
}

if (output) {
const outputDir = path.dirname(output);
mkdirp(outputDir);
fs.writeFileSync(output, js.code);
console.error(`wrote ${path.relative(process.cwd(), output)}`); // eslint-disable-line no-console
if (sourceMap && !inline) {
fs.writeFileSync(`${output}.map`, js.map);
console.error(`wrote ${path.relative(process.cwd(), `${output}.map`)}`); // eslint-disable-line no-console
}
} else {
process.stdout.write(js.code);
}
}

function getName(input) {
return path
.basename(input)
.replace(path.extname(input), '')
.replace(/[^a-zA-Z_$0-9]+/g, '_')
.replace(/^_/, '')
.replace(/_$/, '')
.replace(/^(\d)/, '_$1');
}
17 changes: 17 additions & 0 deletions src/cli/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import clorox from 'clorox';

function stderr(msg) {
console.error(msg); // eslint-disable-line no-console
}

export default function error(err) {
stderr(`${clorox.red(err.message || err)}`);

if (err.frame) {
stderr(err.frame); // eslint-disable-line no-console
} else if (err.stack) {
stderr(`${clorox.grey(err.stack)}`);
}

process.exit(1);
}
30 changes: 30 additions & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import sade from 'sade';
import * as pkg from '../../package.json';

const prog = sade('svelte-cli').version(pkg.version);

prog
.command('compile <input>')

.option('-o, --output', 'Output (if absent, prints to stdout)')
.option('-f, --format', 'Type of output (amd, cjs, es, iife, umd)')
.option('-g, --globals', 'Comma-separate list of `module ID:Global` pairs')
.option('-n, --name', 'Name for IIFE/UMD export (inferred from filename by default)')
.option('-m, --sourcemap', 'Generate sourcemap (`-m inline` for inline map)')
.option('-d, --dev', 'Add dev mode warnings and errors')
.option('--amdId', 'ID for AMD module (default is anonymous)')
.option('--generate', 'Change generate format between `dom` and `ssr`')
.option('--no-css', `Don't include CSS (useful with SSR)`)
.option('--immutable', 'Support immutable data structures')

.example('compile App.html > App.js')
.example('compile src -o dest')
.example('compile -f umd MyComponent.html > MyComponent.js')

.action((input, opts) => {
import('./compile.js').then(({ compile }) => {
compile(input, opts);
});
})

.parse(process.argv);
2 changes: 2 additions & 0 deletions svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env node
require('./cli/index.ts.js');
82 changes: 82 additions & 0 deletions test/cli/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const fs = require('fs');
const path = require('path');
const child_process = require('child_process');
const assert = require('assert');
const glob = require('tiny-glob/sync.js');

const bin = path.resolve(`svelte`);

function normalize(str) {
return str
.replace(/^\s+$/gm, '')
.replace(/generated by Svelte v[.\d]+/, `generated by Svelte vx.y.z`)
.trim();
}

const cwd = process.cwd();

describe('svelte-cli', () => {
afterEach(() => {
process.chdir(cwd);
});

fs.readdirSync('test/cli/samples').forEach(dir => {
if (dir[0] === '.') return;

// append .solo to test dir to only run that test
const solo = /\.solo$/.test(dir);

(solo ? it.only : it)(dir, done => {
process.chdir(`${__dirname}/samples/${dir}`);

const command = fs.readFileSync('command.sh', 'utf-8');

child_process.exec(`
alias svelte=${bin}
mkdir -p actual
rm -rf actual/*
${command}
`, (err, stdout, stderr) => {
if (err) {
done(err);
return;
}

const actual = glob('**', { cwd: 'actual', filesOnly: true })
.map(file => {
return {
file,
contents: normalize(fs.readFileSync(`actual/${file}`, 'utf-8'))
};
});

const expected = glob('**', { cwd: 'expected', filesOnly: true })
.map(file => {
return {
file,
contents: normalize(
fs.readFileSync(`expected/${file}`, 'utf-8')
)
};
});

console.log(actual);
console.log(expected);

actual.forEach((a, i) => {
const e = expected[i];

assert.equal(a.file, e.file, 'File list mismatch');

if (/\.map$/.test(a.file)) {
assert.deepEqual(JSON.parse(a.contents), JSON.parse(e.contents));
} else {
assert.equal(a.contents, e.contents);
}
});

done();
});
});
});
});
1 change: 1 addition & 0 deletions test/cli/samples/basic/command.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
svelte compile src/Main.html > actual/Main.js
Loading

0 comments on commit 4dcde4b

Please sign in to comment.