Skip to content

integrate CLI #1389

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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