Skip to content
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

feat(typescript): better error when tslib is not installed #793

Merged
merged 2 commits into from
Feb 5, 2021
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
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ module.exports = {
tsx: 'never'
}
],
'import/prefer-default-export': 'off',
'import/no-namespace': 'off',
'import/no-named-export': 'off',
'no-redeclare': 'off',
Expand Down
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,12 @@
"@ava/babel": "^1.0.1",
"@typescript-eslint/eslint-plugin": "^4.9.0",
"@typescript-eslint/parser": "^4.9.0",
"ava": "^3.13.0",
"ava": "^3.15.0",
"chalk": "^4.1.0",
"codecov-lite": "^1.0.3",
"del-cli": "^3.0.1",
"eslint-config-rollup": "^1.0.0",
"esm": "^3.2.25",
"execa": "^4.0.3",
"globby": "^11.0.1",
"husky": "^4.2.5",
"lint-staged": "^10.5.2",
Expand All @@ -42,7 +41,6 @@
"prettier-plugin-package": "^1.3.0",
"ts-node": "^8.10.2",
"tsconfig-paths": "^3.9.0",
"tslib": "^2.0.0",
"typescript": "^3.9.7",
"yaml": "^1.10.0"
},
Expand Down
8 changes: 4 additions & 4 deletions packages/typescript/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import * as path from 'path';

import { Plugin, SourceDescription } from 'rollup';
import { Plugin, RollupOptions, SourceDescription } from 'rollup';
import type { Watch } from 'typescript';

import { RollupTypescriptOptions } from '../types';

import createFormattingHost from './diagnostics/host';
import createModuleResolver from './moduleResolution';
import getPluginOptions from './options/plugin';
import { getPluginOptions } from './options/plugin';
import { emitParsedOptionsErrors, parseTypescriptConfig } from './options/tsconfig';
import { validatePaths, validateSourceMap } from './options/validate';
import findTypescriptOutput, { getEmittedFile } from './outputFile';
Expand Down Expand Up @@ -44,10 +44,10 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi
return {
name: 'typescript',

buildStart() {
buildStart(rollupOptions: RollupOptions) {
emitParsedOptionsErrors(ts, this, parsedOptions);

preflight(parsedOptions, this);
preflight({ config: parsedOptions, context: this, rollupOptions, tslib });

// Fixes a memory leak https://github.com/rollup/plugins/issues/322
if (!program) {
Expand Down
6 changes: 3 additions & 3 deletions packages/typescript/src/options/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createFilter } from '@rollup/pluginutils';
import * as defaultTs from 'typescript';

import { RollupTypescriptOptions, PartialCompilerOptions } from '../../types';
import getTsLibPath from '../tslib';
import { getTsLibPath } from '../tslib';

/**
* Separate the Rollup plugin options from the Typescript compiler options,
Expand All @@ -14,7 +14,7 @@ import getTsLibPath from '../tslib';
* - `typescript`: Instance of Typescript library (possibly custom).
* - `tslib`: ESM code from the tslib helper library (possibly custom).
*/
export default function getPluginOptions(options: RollupTypescriptOptions) {
export const getPluginOptions = (options: RollupTypescriptOptions) => {
const {
cacheDir,
exclude,
Expand All @@ -37,4 +37,4 @@ export default function getPluginOptions(options: RollupTypescriptOptions) {
tslib: tslib || getTsLibPath(),
transformers
};
}
};
25 changes: 20 additions & 5 deletions packages/typescript/src/preflight.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
import { PluginContext } from 'rollup';
import { PluginContext, RollupOptions } from 'rollup';
import { ModuleKind } from 'typescript';

import { TypeScriptConfig } from './options/tsconfig';
// import { resolveIdAsync } from './tslib';

const moduleError = `
Rollup requires that TypeScript produces ES Modules. Unfortunately your configuration specifies a
interface PreflightOptions {
config: TypeScriptConfig;
context: PluginContext;
rollupOptions: RollupOptions;
tslib: any;
}

const pluginName = '@rollup/plugin-typescript';
const moduleErrorMessage = `
${pluginName}: Rollup requires that TypeScript produces ES Modules. Unfortunately your configuration specifies a
"module" other than "esnext". Unless you know what you're doing, please change "module" to "esnext"
in the target tsconfig.json file or plugin options.`.replace(/\n/g, '');

const tsLibErrorMessage = `${pluginName}: Could not find module 'tslib', which is required by this plugin. Is it installed?`;

let undef;
const validModules = [ModuleKind.ES2015, ModuleKind.ES2020, ModuleKind.ESNext, undef];

// eslint-disable-next-line import/prefer-default-export
export const preflight = (config: TypeScriptConfig, context: PluginContext) => {
export const preflight = ({ config, context, rollupOptions, tslib }: PreflightOptions) => {
if (!validModules.includes(config.options.module)) {
context.warn(`@rollup/plugin-typescript: ${moduleError}`);
context.warn(moduleErrorMessage);
}

if (!rollupOptions.preserveModules && tslib === null) {
context.error(tsLibErrorMessage);
}
};
30 changes: 22 additions & 8 deletions packages/typescript/src/tslib.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
import resolveId, { AsyncOpts } from 'resolve';
import resolve, { SyncOpts } from 'resolve';

const resolveIdAsync = (file: string, opts: AsyncOpts) =>
new Promise<string>((fulfil, reject) =>
resolveId(file, opts, (err, contents) => (err || typeof contents === 'undefined' ? reject(err) : fulfil(contents)))
);
// const resolveIdAsync = (file: string, opts: AsyncOpts) =>
// new Promise<string>((fulfil, reject) =>
// resolveId(file, opts, (err, contents) =>
// err || typeof contents === 'undefined' ? reject(err) : fulfil(contents)
// )
// );

const resolveId = (file: string, opts: SyncOpts) => resolve.sync(file, opts);

/**
* Returns code asynchronously for the tslib helper library.
*/
export default function getTsLibPath() {
return resolveIdAsync('tslib/tslib.es6.js', { basedir: __dirname });
}
export const getTsLibPath = () => {
// Note: This isn't preferable, but we've no other way to test this bit. Removing the tslib devDep
// during the test run doesn't work due to the nature of the pnpm flat node_modules, and
// other workspace dependencies that depenend upon tslib.
try {
// eslint-disable-next-line no-underscore-dangle
return resolveId(process.env.__TSLIB_TEST_PATH__ || 'tslib/tslib.es6.js', {
basedir: __dirname
});
} catch (_) {
return null;
}
};
32 changes: 32 additions & 0 deletions packages/typescript/test/snapshots/tslib.ts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Snapshot report for `test/tslib.ts`

The actual snapshot is saved in `tslib.ts.snap`.

Generated by [AVA](https://avajs.dev).

## fails on bad tslib path

> Snapshot 1

Error {
code: 'ENOENT',
errno: -2,
path: 'fixtures/joker/tslib.js',
syscall: 'open',
watchFiles: [
'packages/typescript/test/fixtures/overriding-tslib/main.ts',
'fixtures/joker/tslib.js',
],
message: 'Could not load fixtures/joker/tslib.js (imported by fixtures/overriding-tslib/main.ts): ENOENT: no such file or directory, open \'fixtures/joker/tslib.js\'',
}

## fails without tslib installed

> Snapshot 1

Error {
code: 'PLUGIN_ERROR',
hook: 'buildStart',
plugin: 'typescript',
message: '@rollup/plugin-typescript: Could not find module \'tslib\', which is required by this plugin. Is it installed?',
}
Binary file added packages/typescript/test/snapshots/tslib.ts.snap
Binary file not shown.
59 changes: 2 additions & 57 deletions packages/typescript/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,14 @@ const test = require('ava');
const { rollup, watch } = require('rollup');
const ts = require('typescript');

const { getCode, testBundle } = require('../../../util/test');
const { evaluateBundle, getCode, onwarn } = require('../../../util/test');

const typescript = require('..');

test.beforeEach(() => process.chdir(__dirname));

const outputOptions = { format: 'esm' };

async function evaluateBundle(bundle) {
const { module } = await testBundle(null, bundle);
return module.exports;
}

function onwarn(warning) {
// eslint-disable-next-line no-console
console.warn(warning.toString());
}

test.serial('runs code through typescript', async (t) => {
const bundle = await rollup({
input: 'fixtures/basic/main.ts',
Expand Down Expand Up @@ -421,7 +411,7 @@ test.serial('ignore type errors if noEmitOnError is false', async (t) => {

t.true(code.includes(`console.log('hello world')`));

t.is(warnings.length, 1);
t.is(warnings.length, 2);

t.is(warnings[0].code, 'PLUGIN_WARNING');
t.is(warnings[0].plugin, 'typescript');
Expand Down Expand Up @@ -491,38 +481,6 @@ test.serial('supports overriding the TypeScript version', async (t) => {
t.is(result, 1337);
});

test.serial('supports overriding tslib with a custom path', async (t) => {
const bundle = await rollup({
input: 'fixtures/overriding-tslib/main.ts',
plugins: [
typescript({
tsconfig: 'fixtures/overriding-tslib/tsconfig.json',
tslib: 'fixtures/overriding-tslib/tslib.js'
})
],
onwarn
});
const code = await evaluateBundle(bundle);

t.is(code.myParent.baseMethod(), 'base method');
});

test.serial('supports overriding tslib with a custom path in a promise', async (t) => {
const bundle = await rollup({
input: 'fixtures/overriding-tslib/main.ts',
plugins: [
typescript({
tsconfig: 'fixtures/overriding-tslib/tsconfig.json',
tslib: Promise.resolve('fixtures/overriding-tslib/tslib.js')
})
],
onwarn
});
const code = await evaluateBundle(bundle);

t.is(code.myParent.baseMethod(), 'base method');
});

test.serial('should not resolve .d.ts files', async (t) => {
const bundle = await rollup({
input: 'fixtures/dts/main.ts',
Expand Down Expand Up @@ -679,19 +637,6 @@ test.serial('should throw on bad options', async (t) => {
]);
});

test.serial('creates _tslib.js file when preserveModules is used', async (t) => {
const bundle = await rollup({
input: 'fixtures/preserve-modules/main.ts',
plugins: [typescript({ tsconfig: 'fixtures/preserve-modules/tsconfig.json' })],
preserveModules: true,
onwarn
});

const files = await getCode(bundle, { format: 'es' }, true);
t.true(files[0].fileName.includes('main.js'), files[0].fileName);
t.true(files[1].fileName.includes('tslib.es6.js'), files[1].fileName);
});

test.serial('should handle re-exporting types', async (t) => {
const bundle = await rollup({
input: 'fixtures/reexport-type/main.ts',
Expand Down
4 changes: 4 additions & 0 deletions packages/typescript/test/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "../tsconfig.json",
"include": ["."]
}
103 changes: 103 additions & 0 deletions packages/typescript/test/tslib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { platform } from 'os';

import test from 'ava';
import { rollup, RollupError } from 'rollup';

import typescript from '..';

import { evaluateBundle, getCode, onwarn } from '../../../util/test';

test.beforeEach(() => process.chdir(__dirname));

test.serial('supports overriding tslib with a custom path', async (t) => {
const bundle = await rollup({
input: 'fixtures/overriding-tslib/main.ts',
plugins: [
typescript({
tsconfig: 'fixtures/overriding-tslib/tsconfig.json',
tslib: 'fixtures/overriding-tslib/tslib.js'
})
],
onwarn
});
const code = await evaluateBundle(bundle);

t.is((code as any).myParent.baseMethod(), 'base method');
});

test.serial('supports overriding tslib with a custom path in a promise', async (t) => {
const options = {
tsconfig: 'fixtures/overriding-tslib/tsconfig.json',
tslib: Promise.resolve('fixtures/overriding-tslib/tslib.js')
};
const bundle = await rollup({
input: 'fixtures/overriding-tslib/main.ts',
plugins: [typescript(options)],
onwarn
});
const code = await evaluateBundle(bundle);

t.is((code as any).myParent.baseMethod(), 'base method');
});

test.serial('fails on bad tslib path', async (t) => {
const fail = () =>
rollup({
input: 'fixtures/overriding-tslib/main.ts',
plugins: [
typescript({
tsconfig: 'fixtures/overriding-tslib/tsconfig.json',
tslib: 'fixtures/joker/tslib.js'
})
],
onwarn
});

const error = (await t.throwsAsync(fail)) as RollupError;

// Note: I'm done fucking around with Windows paths
if (platform() === 'win32') {
t.pass();
return;
}

if (error.watchFiles) {
let [filePath] = error.watchFiles;
filePath = filePath.substring(filePath.indexOf('packages'));
error.watchFiles[0] = filePath;
}

t.snapshot(error);
});

test.serial('fails without tslib installed', async (t) => {
const fail = () =>
rollup({
input: 'fixtures/overriding-tslib/main.ts',
plugins: [typescript({ tsconfig: 'fixtures/overriding-tslib/tsconfig.json' })],
onwarn
});

// eslint-disable-next-line no-underscore-dangle
process.env.__TSLIB_TEST_PATH__ = 'badtslib/tslib.es6.js';

const error = await t.throwsAsync(fail);

// eslint-disable-next-line no-underscore-dangle, no-undefined
process.env.__TSLIB_TEST_PATH__ = '';

t.snapshot(error);
});

test.serial('creates _tslib.js file when preserveModules is used', async (t) => {
const bundle = await rollup({
input: 'fixtures/preserve-modules/main.ts',
plugins: [typescript({ tsconfig: 'fixtures/preserve-modules/tsconfig.json' })],
preserveModules: true,
onwarn
});

const files = await getCode(bundle, { format: 'es' }, true);
t.true(files[0].fileName.includes('main.js'), files[0].fileName);
t.true(files[1].fileName.includes('tslib.es6.js'), files[1].fileName);
});
Loading