Skip to content

Commit

Permalink
Update build process to allow for future additional entrypoints (#8)
Browse files Browse the repository at this point in the history
* Add updated rollup config for multiple entrypoints

* Add graphqlsp entrypoint

* Change default to 'auto' on exports

* Remove LSP entrypoint
  • Loading branch information
kitten authored Jan 13, 2024
1 parent 0d1054c commit 3bc09bc
Showing 1 changed file with 214 additions and 135 deletions.
349 changes: 214 additions & 135 deletions scripts/rollup.config.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import fs from 'node:fs/promises';
import path from 'node:path/posix';
import { readFileSync } from 'node:fs';

import * as prettier from 'prettier';
Expand All @@ -8,6 +10,61 @@ import terser from '@rollup/plugin-terser';
import cjsCheck from 'rollup-plugin-cjs-check';
import dts from 'rollup-plugin-dts';

const normalize = name => []
.concat(name)
.join(' ')
.replace(/[@\s/.]+/g, ' ')
.trim()
.replace(/\s+/, '-')
.toLowerCase();

const extension = name => {
if (/\.d.ts$/.test(name)) {
return '.d.ts';
} else {
return path.extname(name);
}
};

const meta = JSON.parse(readFileSync('package.json'));
const name = normalize(meta.name);

const externalModules = [
...Object.keys(meta.dependencies || {}),
...Object.keys(meta.peerDependencies || {}),
];

const external = new RegExp(`^(${externalModules.join('|')})($|/)`);

const exports = {};
for (const key in meta.exports) {
const entry = meta.exports[key];
if (typeof entry === 'object' && !!entry.source) {
const entryPath = normalize(key);
const entryName = normalize([name, entryPath]);
exports[entryName] = {
path: entryPath,
...entry,
};
}
}

const commonConfig = {
input: Object.entries(exports).reduce((input, [exportName, entry]) => {
input[exportName] = entry.source;
return input;
}, {}),
onwarn: () => {},
external(id) {
return external.test(id);
},
treeshake: {
unknownGlobalSideEffects: false,
tryCatchDeoptimization: false,
moduleSideEffects: false,
},
};

const commonPlugins = [
resolve({
extensions: ['.mjs', '.js', '.ts'],
Expand All @@ -22,154 +79,176 @@ const commonPlugins = [
}),
];

const packageJson = JSON.parse(readFileSync('package.json'));
const externalModules = Object.keys(packageJson.dependencies);
const externalPredicate = new RegExp(`^(${externalModules.join('|')})($|/)`);
const external = (id) => externalPredicate.test(id);
const commonOutput = {
dir: './',
exports: 'auto',
sourcemap: true,
sourcemapExcludeSources: false,
hoistTransitiveImports: false,
indent: false,
freeze: false,
strict: false,
generatedCode: {
preset: 'es5',
reservedNamesAsProps: false,
objectShorthand: false,
constBindings: false,
},
};

const jsPlugins = [
...commonPlugins,
const outputPlugins = [
{
name: 'outputPackageJsons',
async writeBundle() {
for (const key in exports) {
const entry = exports[key];
if (entry.path) {
const output = path.relative(entry.path, process.cwd());
const json = JSON.stringify({
name: key,
private: true,
version: '0.0.0',
main: path.join(output, entry.require),
module: path.join(output, entry.import),
types: path.join(output, entry.types),
source: path.join(output, entry.source),
exports: {
'.': {
types: path.join(output, entry.types),
import: path.join(output, entry.import),
require: path.join(output, entry.require),
source: path.join(output, entry.source),
},
},
}, null, 2);

babel({
babelrc: false,
babelHelpers: 'bundled',
extensions: ['mjs', 'js', 'jsx', 'ts', 'tsx'],
exclude: 'node_modules/**',
presets: [],
plugins: [
'@babel/plugin-transform-typescript',
'@babel/plugin-transform-block-scoping',
],
}),
];
await fs.mkdir(entry.path, { recursive: true });
await fs.writeFile(path.join(entry.path, 'package.json'), json);
}
}
},
},

cjsCheck(),

const dtsPlugins = [
...commonPlugins,
dts(),
terser({
warnings: true,
ecma: 2015,
keep_fnames: true,
ie8: false,
compress: {
pure_getters: true,
toplevel: true,
booleans_as_integers: false,
keep_fnames: true,
keep_fargs: true,
if_return: false,
ie8: false,
sequences: false,
loops: false,
conditionals: false,
join_vars: false,
},
mangle: {
module: true,
keep_fnames: true,
},
output: {
beautify: true,
braces: true,
indent_level: 2,
},
}),
];

const output = format => {
const extension = format === 'esm' ? '.mjs' : '.js';
return {
chunkFileNames: '[hash]' + extension,
entryFileNames: '[name]' + extension,
dir: './dist',
exports: 'named',
sourcemap: true,
sourcemapExcludeSources: false,
indent: false,
freeze: false,
strict: false,
format,
// NOTE: All below settings are important for cjs-module-lexer to detect the export
// When this changes (and terser mangles the output) this will interfere with Node.js ESM intercompatibility
esModule: format !== 'esm',
externalLiveBindings: format !== 'esm',
export default [
{
...commonConfig,
plugins: [
cjsCheck(),

terser({
warnings: true,
ecma: 2015,
keep_fnames: true,
ie8: false,
compress: {
pure_getters: true,
toplevel: true,
booleans_as_integers: false,
keep_fnames: true,
keep_fargs: true,
if_return: false,
ie8: false,
sequences: false,
loops: false,
conditionals: false,
join_vars: false,
...commonPlugins,
babel({
babelrc: false,
babelHelpers: 'bundled',
extensions: ['mjs', 'js', 'jsx', 'ts', 'tsx'],
exclude: 'node_modules/**',
presets: [],
plugins: [
'@babel/plugin-transform-typescript',
'@babel/plugin-transform-block-scoping',
],
}),
],
output: [
{
...commonOutput,
format: 'esm',
chunkFileNames(chunk) {
return `dist/chunks/[name]-chunk${extension(chunk.name) || '.mjs'}`;
},
entryFileNames(chunk) {
return chunk.isEntry
? path.normalize(exports[chunk.name].import)
: `dist/[name].mjs`;
},
mangle: {
module: true,
keep_fnames: true,
plugins: outputPlugins,
},
{
...commonOutput,
format: 'cjs',
esModule: true,
externalLiveBindings: true,
chunkFileNames(chunk) {
return `dist/chunks/[name]-chunk${extension(chunk.name) || '.js'}`;
},
output: {
beautify: true,
braces: true,
indent_level: 2,
entryFileNames(chunk) {
return chunk.isEntry
? path.normalize(exports[chunk.name].require)
: `dist/[name].js`;
},
}),
plugins: outputPlugins,
},
],
generatedCode: {
preset: 'es5',
reservedNamesAsProps: false,
objectShorthand: false,
constBindings: false,
},
};
};

const commonConfig = {
input: {
'gql-tada': './src/index.ts',
},
onwarn: () => {},
external,
treeshake: {
unknownGlobalSideEffects: false,
tryCatchDeoptimization: false,
moduleSideEffects: false,
},
};

const jsConfig = {
...commonConfig,
plugins: jsPlugins,
output: [
output('esm'),
output('cjs'),
],
};

const dtsConfig = {
...commonConfig,
input: {
'gql-tada': './src/index.ts',
},
onwarn: () => {},
external,
plugins: dtsPlugins,
treeshake: {
unknownGlobalSideEffects: false,
tryCatchDeoptimization: false,
moduleSideEffects: false,
},
output: {
dir: './dist',
entryFileNames: '[name].d.ts',
format: 'es',
{
...commonConfig,
plugins: [
{
renderChunk(code, chunk) {
if (chunk.fileName.endsWith('d.ts')) {
const gqlImportRe = /(import\s+(?:[*\s{}\w\d]+)\s*from\s*'graphql';?)/g;
code = code.replace(gqlImportRe, x => '/*!@ts-ignore*/\n' + x);

code = prettier.format(code, {
filepath: chunk.fileName,
parser: 'typescript',
singleQuote: true,
tabWidth: 2,
printWidth: 100,
trailingComma: 'es5',
});

return code;
}
},
},
...commonPlugins,
dts(),
],
},
};
output: {
...commonOutput,
sourcemap: false,
format: 'dts',
chunkFileNames(chunk) {
return `dist/chunks/[name]-chunk${extension(chunk.name) || '.d.ts'}`;
},
entryFileNames(chunk) {
return chunk.isEntry
? path.normalize(exports[chunk.name].types)
: `dist/[name].d.ts`;
},
plugins: [
{
renderChunk(code, chunk) {
if (chunk.fileName.endsWith('d.ts')) {
const gqlImportRe = /(import\s+(?:[*\s{}\w\d]+)\s*from\s*'graphql';?)/g;
code = code.replace(gqlImportRe, x => '/*!@ts-ignore*/\n' + x);

export default [
jsConfig,
dtsConfig,
code = prettier.format(code, {
filepath: chunk.fileName,
parser: 'typescript',
singleQuote: true,
tabWidth: 2,
printWidth: 100,
trailingComma: 'es5',
});

return code;
}
},
},
],
},
},
];

0 comments on commit 3bc09bc

Please sign in to comment.