Skip to content

Commit

Permalink
Add typescript plugin (#1211)
Browse files Browse the repository at this point in the history
* add typescript plugin, add tests

* update docs and templates

* Update plugin.js
  • Loading branch information
FredKSchott committed Oct 6, 2020
1 parent 2b3670f commit d86e495
Show file tree
Hide file tree
Showing 18 changed files with 225 additions and 22 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ For starter apps and templates, see [create-snowpack-app](./create-snowpack-app)
### Dev Environment

- [@snowpack/plugin-dotenv](./plugins/plugin-dotenv)
- [@snowpack/plugin-typescript](./plugins/plugin-typescript)

### Build

Expand All @@ -49,10 +50,10 @@ For starter apps and templates, see [create-snowpack-app](./create-snowpack-app)

### Bundle

- [@snowpack/plugin-parcel](./plugins/plugin-parcel)
- [@snowpack/plugin-webpack](./plugins/plugin-webpack)
- [@snowpack/plugin-parcel](./plugins/plugin-parcel)

### Advanced Plugins
### Utility Plugins

- [@snowpack/plugin-build-script](./plugins/plugin-build-script)
- [@snowpack/plugin-run-script](./plugins/plugin-run-script)
Expand Down
1 change: 1 addition & 0 deletions create-snowpack-app/app-scripts-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@babel/preset-typescript": "^7.10.4",
"@snowpack/plugin-babel": "^2.1.2",
"@snowpack/plugin-dotenv": "^2.0.3",
"@snowpack/plugin-typescript": "^1.0.0",
"@snowpack/plugin-react-refresh": "^2.3.1",
"babel-jest": "^26.2.2",
"babel-preset-react-app": "^9.1.2",
Expand Down
2 changes: 1 addition & 1 deletion create-snowpack-app/app-scripts-react/snowpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module.exports = {
'@snowpack/plugin-react-refresh',
'@snowpack/plugin-babel',
'@snowpack/plugin-dotenv',
...(isTS ? [['@snowpack/plugin-run-script', {cmd: 'tsc --noEmit', watch: '$1 --watch'}]] : []),
...(isTS ? ['@snowpack/plugin-typescript'] : []),
],
devOptions: {},
installOptions: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
},
"devDependencies": {
"@snowpack/plugin-run-script": "^2.1.4",
"@snowpack/plugin-typescript": "^1.0.0",
"@types/canvas-confetti": "^1.0.0",
"@types/snowpack-env": "^2.3.0",
"prettier": "^2.0.5",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
"src": "/_dist_"
},
"plugins": [
[
"@snowpack/plugin-run-script",
{ "cmd": "tsc --noEmit", "watch": "$1 --watch" }
]
"@snowpack/plugin-typescript"
],
"installOptions": {
"installTypes": true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@snowpack/plugin-babel": "^2.1.2",
"@snowpack/plugin-dotenv": "^2.0.3",
"@snowpack/plugin-run-script": "^2.1.4",
"@snowpack/plugin-typescript": "^1.0.0",
"@types/snowpack-env": "^2.3.0",
"prettier": "^2.0.5",
"snowpack": "^2.13.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ module.exports = {
plugins: [
'@snowpack/plugin-babel',
'@snowpack/plugin-dotenv',
[
'@snowpack/plugin-run-script',
{ cmd: 'tsc --noEmit', watch: '$1 --watch' },
],
"@snowpack/plugin-typescript"
],
};
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@snowpack/app-scripts-react": "^1.12.1",
"@snowpack/plugin-dotenv": "^2.0.3",
"@snowpack/plugin-react-refresh": "^2.3.1",
"@snowpack/plugin-typescript": "^1.0.0",
"@testing-library/jest-dom": "^5.5.0",
"@testing-library/react": "^10.0.3",
"@types/react": "^16.9.49",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ module.exports = {
plugins: [
'@snowpack/plugin-react-refresh',
'@snowpack/plugin-dotenv',
['@snowpack/plugin-run-script', {cmd: 'tsc --noEmit', watch: '$1 --watch'}],
"@snowpack/plugin-typescript"
],
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@snowpack/plugin-dotenv": "^2.0.3",
"@snowpack/plugin-run-script": "^2.1.4",
"@snowpack/plugin-svelte": "^2.1.1",
"@snowpack/plugin-typescript": "^1.0.0",
"@testing-library/jest-dom": "^5.5.0",
"@testing-library/svelte": "^3.0.0",
"@tsconfig/svelte": "^1.0.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ module.exports = {
plugins: [
'@snowpack/plugin-svelte',
'@snowpack/plugin-dotenv',
"@snowpack/plugin-typescript",
[
'@snowpack/plugin-run-script',
{cmd: 'svelte-check --output human', watch: '$1 --watch', output: 'stream'},
],
['@snowpack/plugin-run-script', {cmd: 'tsc --noEmit', watch: '$1 --watch'}],
],
};
2 changes: 1 addition & 1 deletion docs/docs/07-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ This plugin allows you to connect any CLI into your build process. Just give it
// [npm install @snowpack/plugin-run-script]
{
"plugins": [
["@snowpack/plugin-run-script", { "cmd": "tsc --noEmit", "watch": "$1 --watch"}]
["@snowpack/plugin-run-script", { "cmd": "eleventy", "watch": "$1 --watch" }]
],
}
```
Expand Down
11 changes: 3 additions & 8 deletions docs/docs/08-guides.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,13 @@ Snowpack has built-in support to handle `.jsx` & `.tsx` source files in your app

### TypeScript

Snowpack has built-in support to handle `.ts` & `.tsx` source files in your application.
Snowpack includes built-in support to build all TypeScript source files (`.ts` & `.tsx`) in your application.

Snowpack supports live TypeScript type checking right in the Snowpack CLI dev console. Connect the TypeScript compiler (`tsc`) into your workflow using the snippet below.
For automatic TypeScript type checking during development, add the official [@snowpack/plugin-typescript](https://www.npmjs.com/package/@snowpack/plugin-typescript) plugin to your Snowpack config file. This plugin adds automatic `tsc` type checking results right in the Snowpack dev console.

```js
// snowpack.config.json
// Example: Connect TypeScript CLI (tsc) reporting to Snowpack
{
"plugins": [
["@snowpack/plugin-run-script", {"cmd": "tsc --noEmit", "watch": "$1 --watch"}]
]
}
"plugins": ["@snowpack/plugin-typescript"]
```

### Babel
Expand Down
73 changes: 73 additions & 0 deletions plugins/plugin-run-script/test/plugin.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const plugin = require('../plugin.js');
const {EventEmitter} = require('events');

jest.mock('execa');
const execa = require('execa');

// NOTE(fks): This is skipped due to refactoring happening in https://github.com/pikapkg/snowpack/pull/1203
// Once that PR is merged, this should be safe to unskip
describe.skip('plugin-run-script', () => {
const DEFAULT_OPTIONS = {
cmd: 'CMD',
watch: '$1 --additional-test-watch-options',
};
let execaResult, execaFn;

beforeEach(() => {
execa.command.mockClear();
execaResult = {stderr: new EventEmitter(), stdout: new EventEmitter()};
execaFn = jest.fn(() => execaResult);
execa.command = execaFn;
});

test('returns the execa command promise', async () => {
const p = plugin(null, DEFAULT_OPTIONS);
const result = await p.run({isDev: false, log: jest.fn});
expect(result).toEqual(result);
});
test('calls the given "cmd" command when isDev=false', async () => {
const p = plugin(null, {cmd: 'CMD'});
await p.run({isDev: false, log: jest.fn});
expect(execaFn.mock.calls[0][0]).toMatch('CMD');
});
test('calls the given "watch" command when isDev=true', async () => {
const p = plugin(null, {cmd: 'CMD', watch: '$1 --additional-test-watch-options'});
await p.run({isDev: true, log: jest.fn});
expect(execaFn.mock.calls[0][0]).toMatch('cmd --additional-test-watch-options');
});
test('handles command output in "stream" mode', async () => {
const logFn = jest.fn();
const p = plugin(null, {...DEFAULT_OPTIONS, output: 'stream'});
await p.run({isDev: false, log: logFn});
execaResult.stdout.emit('data', Buffer.from('STDOUT_TEST_MESSAGE'));
execaResult.stderr.emit('data', Buffer.from('STDERR_TEST_MESSAGE'));
expect(logFn.mock.calls).toEqual([
['CONSOLE_INFO', {id: 'CMD', msg: 'STDOUT_TEST_MESSAGE'}],
['CONSOLE_INFO', {id: 'CMD', msg: 'STDERR_TEST_MESSAGE'}],
]);
});
test('handles command output in "dashboard" mode', async () => {
const logFn = jest.fn();
const p = plugin(null, {...DEFAULT_OPTIONS, output: 'dashboard'});
await p.run({isDev: false, log: logFn});
execaResult.stdout.emit('data', Buffer.from('STDOUT_TEST_MESSAGE'));
execaResult.stderr.emit('data', Buffer.from('STDERR_TEST_MESSAGE'));
expect(logFn.mock.calls).toEqual([
['WORKER_MSG', {level: 'log', msg: 'STDOUT_TEST_MESSAGE'}],
['WORKER_MSG', {level: 'log', msg: 'STDERR_TEST_MESSAGE'}],
]);
});
test('handles clear character output in "dashboard" mode', async () => {
const logFn = jest.fn();
const p = plugin(null, {...DEFAULT_OPTIONS, output: 'dashboard'});
await p.run({isDev: false, log: logFn});
execaResult.stderr.emit('data', Buffer.from('\u001bcTEST_CLEAR_MESSAGE'));
execaResult.stderr.emit('data', Buffer.from('\x1BcTEST_CLEAR_MESSAGE'));
expect(logFn.mock.calls).toEqual([
['WORKER_RESET', {}],
['WORKER_MSG', {level: 'log', msg: 'TEST_CLEAR_MESSAGE'}],
['WORKER_RESET', {}],
['WORKER_MSG', {level: 'log', msg: 'TEST_CLEAR_MESSAGE'}],
]);
});
});
25 changes: 25 additions & 0 deletions plugins/plugin-typescript/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# @snowpack/plugin-typescript

This plugin adds TypeScript type checking to any Snowpack project.

When developing or building your site with Snowpack, this plugin will run TypeScript's `tsc` CLI in your project and pipe the output through Snowpack. Works with all version of TypeScript, as long as TypeScript is installed separately in your project.



## Usage

```bash
npm i @snowpack/plugin-typescript typescript
```

Then add the plugin to your Snowpack config:

```js
// snowpack.config.js

module.exports = {
plugins: [
'@snowpack/plugin-typescript'
],
};
```
22 changes: 22 additions & 0 deletions plugins/plugin-typescript/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"version": "1.0.0",
"name": "@snowpack/plugin-typescript",
"main": "plugin.js",
"license": "MIT",
"homepage": "https://github.com/pikapkg/snowpack/tree/master/plugins/plugin-typescript#readme",
"repository": {
"type": "git",
"url": "https://github.com/pikapkg/snowpack.git",
"directory": "plugins/plugin-typescript"
},
"publishConfig": {
"access": "public"
},
"peerDependencies": {
"typescript": "*"
},
"dependencies": {
"execa": "^4.0.3",
"npm-run-path": "^4.0.1"
}
}
32 changes: 32 additions & 0 deletions plugins/plugin-typescript/plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const execa = require('execa');
const npmRunPath = require('npm-run-path');
const cwd = process.cwd();

function typescriptPlugin() {
return {
name: '@snowpack/plugin-typescript',
async run({isDev, log}) {
const workerPromise = execa.command(`tsc --noEmit ${isDev ? '--watch' : ''}`, {
env: npmRunPath.env(),
extendEnv: true,
windowsHide: false,
cwd,
});
const {stdout, stderr} = workerPromise;
function dataListener(chunk) {
let stdOutput = chunk.toString();
// In --watch mode, handle the "clear" character
if (stdOutput.includes('\u001bc') || stdOutput.includes('\x1Bc')) {
log('WORKER_RESET', {});
stdOutput = stdOutput.replace(/\x1Bc/, '').replace(/\u001bc/, '');
}
log('WORKER_MSG', {level: 'log', msg: stdOutput});
}
stdout && stdout.on('data', dataListener);
stderr && stderr.on('data', dataListener);
return workerPromise;
},
};
}

module.exports = typescriptPlugin;
56 changes: 56 additions & 0 deletions plugins/plugin-typescript/test/plugin.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const plugin = require('../plugin.js');
const {EventEmitter} = require('events');

jest.mock('execa');
const execa = require('execa');

describe('plugin-typescript', () => {
let execaResult, execaFn;

beforeEach(() => {
execa.command.mockClear();
execaResult = {stderr: new EventEmitter(), stdout: new EventEmitter()};
execaFn = jest.fn(() => execaResult);
execa.command = execaFn;
});

test('returns the execa command promise', async () => {
const p = plugin();
const result = await p.run({isDev: false, log: jest.fn});
expect(result).toEqual(result);
});
test('calls "tsc" when isDev=false', async () => {
const p = plugin();
await p.run({isDev: false, log: jest.fn});
expect(execaFn.mock.calls[0][0]).toMatch('tsc');
});
test('calls "tsc --watch" when isDev=true', async () => {
const p = plugin();
await p.run({isDev: true, log: jest.fn});
expect(execaFn.mock.calls[0][0]).toMatch('tsc --watch');
});
test('handles tsc output', async () => {
const logFn = jest.fn();
const p = plugin();
await p.run({isDev: false, log: logFn});
execaResult.stdout.emit('data', Buffer.from('STDOUT_TEST_MESSAGE'));
execaResult.stderr.emit('data', Buffer.from('STDERR_TEST_MESSAGE'));
expect(logFn.mock.calls).toEqual([
['WORKER_MSG', {level: 'log', msg: 'STDOUT_TEST_MESSAGE'}],
['WORKER_MSG', {level: 'log', msg: 'STDERR_TEST_MESSAGE'}],
]);
});
test('handles tsc clear character messages', async () => {
const logFn = jest.fn();
const p = plugin();
await p.run({isDev: false, log: logFn});
execaResult.stderr.emit('data', Buffer.from('\u001bcTEST_CLEAR_MESSAGE'));
execaResult.stderr.emit('data', Buffer.from('\x1BcTEST_CLEAR_MESSAGE'));
expect(logFn.mock.calls).toEqual([
['WORKER_RESET', {}],
['WORKER_MSG', {level: 'log', msg: 'TEST_CLEAR_MESSAGE'}],
['WORKER_RESET', {}],
['WORKER_MSG', {level: 'log', msg: 'TEST_CLEAR_MESSAGE'}],
]);
});
});

1 comment on commit d86e495

@vercel
Copy link

@vercel vercel bot commented on d86e495 Oct 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.