feat(preset-rollup): provide a preset for code bundling via rollup
This commit is contained in:
commit
dfebcbe9a5
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"preset": "presetter-preset",
|
||||
"config": {
|
||||
"npmignore": ["!/configs/**", "!/templates/**"]
|
||||
},
|
||||
"variable": {
|
||||
"root": "../.."
|
||||
}
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
<div align="center">
|
||||
|
||||
![Logo](https://github.com/alvis/presetter/raw/master/assets/logo.svg)
|
||||
|
||||
🏄🏻 _A collection of opinionated configurations for a building a code bundle via rollup for presetter_
|
||||
|
||||
• [Quick Start](#quick-start) • [Project Structure](#project-structure) • [Customisation](#customisation) • [Scripts](#script-template-summary) •
|
||||
|
||||
[![npm](https://img.shields.io/npm/v/presetter-preset-rollup?style=flat-square)](https://github.com/alvis/presetter/releases)
|
||||
[![build](https://img.shields.io/github/workflow/status/alvis/presetter/code%20test?style=flat-square)](https://github.com/alvis/presetter/actions)
|
||||
[![maintainability](https://img.shields.io/codeclimate/maintainability/alvis/presetter?style=flat-square)](https://codeclimate.com/github/alvis/presetter/maintainability)
|
||||
[![coverage](https://img.shields.io/codeclimate/coverage/alvis/presetter?style=flat-square)](https://codeclimate.com/github/alvis/presetter/test_coverage)
|
||||
[![security](https://img.shields.io/snyk/vulnerabilities/github/alvis/presetter/packages/preset-rollup/package.json.svg?style=flat-square)](https://snyk.io/test/github/alvis/presetter?targetFile=packages/preset-rollup/package.json&style=flat-square)
|
||||
[![dependencies](https://img.shields.io/david/alvis/presetter?path=packages/preset-rollup&style=flat-square)](https://david-dm.org/alvis/presetter?path=packages/preset-rollup)
|
||||
[![license](https://img.shields.io/github/license/alvis/presetter.svg?style=flat-square)](https://github.com/alvis/presetter/blob/master/LICENSE)
|
||||
|
||||
</div>
|
||||
|
||||
## Features
|
||||
|
||||
**presetter-preset-rollup** is an opinionated preset for you to setup rollup in a fraction of time you usually take via [**presetter**](https://github.com/alvis/presetter).
|
||||
|
||||
- 🗞️ Rollup 2
|
||||
- 2️⃣ Dual CJS and ESM modules export by default
|
||||
- 🍄 Common rollup packages included as one single bundle
|
||||
- `@rollup/plugin-commonjs`
|
||||
- `@rollup/plugin-graphql`
|
||||
- `@rollup/plugin-image`
|
||||
- `@rollup/plugin-json`
|
||||
- `@rollup/plugin-yaml`
|
||||
- `rollup` <~ of course including rollup itself
|
||||
- `rollup-plugin-postcss`
|
||||
- `rollup-plugin-ts`
|
||||
- `rollup-plugin-tsconfig-paths`
|
||||
- `rollup-plugin-visualizer`
|
||||
|
||||
## Quick Start
|
||||
|
||||
[**FULL DOCUMENTATION IS AVAILABLE HERE**](https://github.com/alvis/presetter/blob/master/README.md)
|
||||
|
||||
1. Bootstrap your project with `presetter-preset` & `presetter-preset-rollup`
|
||||
|
||||
```shell
|
||||
npx presetter use presetter-preset presetter-preset-rollup
|
||||
```
|
||||
|
||||
That's. One command and you're set.
|
||||
|
||||
After bootstrapping, you would see a lot of configuration files generated, including a `rollup.config.ts` that has all plugins configured properly for you.
|
||||
|
||||
2. Develop and run life cycle scripts provided by the preset
|
||||
|
||||
At this point, all development packages specified in the preset are installed,
|
||||
and now you can try to run some example life cycle scripts (e.g. run prepare).
|
||||
|
||||
![Demo](https://raw.githubusercontent.com/alvis/presetter/master/assets/demo.gif)
|
||||
|
||||
**IMPORTANT**
|
||||
For NodeJS to import the correct export, remember to specify the following in your project's package.json too!
|
||||
|
||||
```json
|
||||
{
|
||||
"main": "lib/index.js",
|
||||
"module": "lib/index.mjs",
|
||||
"types": "lib/index.d.ts",
|
||||
"exports": {
|
||||
"require": "./lib/index.js",
|
||||
"import": "./lib/index.mjs"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
After installing `presetter-preset` & `presetter-preset-rollup`, your project file structure should look like the following or with more configuration file if you also installed `presetter-preset`.
|
||||
|
||||
Implement your business logic under `source` and prepare tests under `spec`.
|
||||
|
||||
**TIPS** You can always change the source directory to other (e.g. src) by setting the `source` variable in `.presetterrc.json`. See the [customisation](https://github.com/alvis/presetter/blob/master/packages/preset-rollup#customisation) section below for more details.
|
||||
|
||||
```
|
||||
(root)
|
||||
├─ .git
|
||||
├─ .preseterrc.json
|
||||
├─ node_modules
|
||||
├─ source
|
||||
│ ├─ <folders>
|
||||
│ ├─ index.ts
|
||||
│ ├─ (auxiliary).ts
|
||||
├─ spec
|
||||
│ ├─ *.spec.ts
|
||||
├─ package.json
|
||||
└─ rollup.config.ts
|
||||
```
|
||||
|
||||
## Customisation
|
||||
|
||||
By default, this preset exports a handy configuration for rollup for a typescript project.
|
||||
But you can further customise (either extending or replacing) the configuration by specifying the change in the config file (`.presetterrc` or `.presetterrc.json`).
|
||||
|
||||
These settings are available in the `config` field in the config file. For directories, the setting is specified in the `variable` field.
|
||||
|
||||
The structure of `.presetterrc` should follow the interface below:
|
||||
|
||||
```ts
|
||||
interface PresetterRC {
|
||||
/** name(s) of the preset e.g. presetter-preset */
|
||||
name: string | string[];
|
||||
/** additional configuration passed to the preset for generating the configuration files */
|
||||
config?: {
|
||||
// ┌─ configuration for other tools via other presets (e.g. presetter-preset)
|
||||
// ...
|
||||
|
||||
/** additional configuration for rollup */
|
||||
rollup?: {
|
||||
// ┌─ any configuration supported by rollup, see https://rollupjs.org/guide/en/#configuration-files
|
||||
// ...
|
||||
|
||||
/** list of plugin and its options */
|
||||
plugins?:
|
||||
| NormalisedRollupConfig['plugins']
|
||||
| Array<
|
||||
| string
|
||||
| [name: string]
|
||||
| [
|
||||
name: string,
|
||||
options:
|
||||
| Record<string, unknown>
|
||||
| `@apply ${string}`
|
||||
| `@import ${string}`
|
||||
| null,
|
||||
]
|
||||
>;
|
||||
};
|
||||
};
|
||||
/** variables to be substituted in templates */
|
||||
variable?: {
|
||||
/** the directory containing all source code (default: source) */
|
||||
source?: string;
|
||||
/** the directory containing all output tile (default: source) */
|
||||
output?: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
For generating `rollup.config.ts`, this preset also support the `@apply` and `@import` directives such that you can also import configuration from other packages or ts/js files.
|
||||
|
||||
The usage of the directives is simple. In any part of the configuration for rollup, you can simply put
|
||||
`@apply package_name` or `@import package_name` and the preset will automatically replace the content with an imported variable. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"rollup": {
|
||||
"plugins": [
|
||||
[
|
||||
"@apply rollup-plugin-postcss[default]",
|
||||
{ "plugins": "@import ./postcss.config[default.plugins]" }
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
will create a `rollup.config.ts` file with the following content:
|
||||
|
||||
```ts
|
||||
import * as import0 from 'rollup-plugin-postcss';
|
||||
import * as import1 from './postcss.config';
|
||||
|
||||
export default {
|
||||
plugins: [import0.default(...[{ plugins: import1.default.plugins }])],
|
||||
};
|
||||
```
|
||||
|
||||
The syntax for both the directives is quite similar.
|
||||
Use `@apply` in a situation that you have to invoke a function from an imported package,
|
||||
such as `rollup-plugin-postcss` in the above example.
|
||||
You can also specify the arguments for the invoked function in the form of `["@apply package", options]`
|
||||
|
||||
For `@import`, use it if you want to import value from another package or ts/js file.
|
||||
For example, `@import ./postcss.config[default.plugins]` would allow you to refer `default.plugins` from `./postcss.config` in the above example.
|
||||
|
||||
In addition to the directives, to specify the plugins for rollup, you can write it in three ways similar to babel.
|
||||
|
||||
1. A object with plugin name as the key and its options as its value e.g. `{'@apply @rollup/plugin-typescript[default]': {options}}`
|
||||
2. Name of a plugin in an array e.g. `['@apply @rollup/plugin-typescript[default]']`
|
||||
3. Doublet of `[plugin name, options]` in an array e.g. `[['@apply @rollup/plugin-typescript[default]', {options}]]`
|
||||
|
||||
## Script Template Summary
|
||||
|
||||
- **`run build`**: Bundle your code via rollup
|
||||
- **`run develop`**: Continuous code build and watch
|
|
@ -0,0 +1,26 @@
|
|||
input: '{source}/index.ts'
|
||||
output:
|
||||
- file: '{output}/index.js'
|
||||
format: cjs
|
||||
sourcemap: true
|
||||
- file: '{output}/index.mjs'
|
||||
format: es
|
||||
sourcemap: true
|
||||
plugins:
|
||||
- '@apply rollup-plugin-ts[default]'
|
||||
- '@apply rollup-plugin-tsconfig-paths[default]'
|
||||
- '@apply @rollup/plugin-node-resolve[default]'
|
||||
- - '@apply @rollup/plugin-commonjs[default]'
|
||||
- extensions:
|
||||
- .js
|
||||
- .jsx
|
||||
- .ts
|
||||
- .tsx
|
||||
- '@apply @rollup/plugin-json[default]'
|
||||
- '@apply @rollup/plugin-graphql[default]'
|
||||
- '@apply @rollup/plugin-image[default]'
|
||||
- '@apply @rollup/plugin-yaml[default]'
|
||||
- - '@apply rollup-plugin-postcss[default]'
|
||||
- inject:
|
||||
insertAt: top
|
||||
- '@apply rollup-plugin-visualizer[default]'
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"name": "presetter-preset-rollup",
|
||||
"version": "0.0.0",
|
||||
"description": "An opinionated presetter preset for using rollup as a bundler",
|
||||
"keywords": [
|
||||
"presetter",
|
||||
"preset"
|
||||
],
|
||||
"homepage": "https://github.com/alvis/presetter#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/alvis/presetter/issues"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "Alvis HT Tang",
|
||||
"email": "alvis@hilbert.space"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/alvis/presetter.git"
|
||||
},
|
||||
"scripts": {
|
||||
"bootstrap": "presetter bootstrap && run prepare",
|
||||
"build": "run build",
|
||||
"coverage": "run coverage",
|
||||
"lint": "run lint",
|
||||
"prepublishOnly": "run prepare && run prepublishOnly",
|
||||
"release": "run-s release:peer:*",
|
||||
"release:peer:presetter": "npm pkg set peerDependencies.presetter=^$(npx -c 'echo $npm_package_version')",
|
||||
"test": "run test",
|
||||
"watch": "run watch"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@rollup/plugin-commonjs": "^20.0.0",
|
||||
"@rollup/plugin-graphql": "^1.0.0",
|
||||
"@rollup/plugin-image": "^2.0.0",
|
||||
"@rollup/plugin-json": "^4.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.0.0",
|
||||
"@rollup/plugin-yaml": "^3.0.0",
|
||||
"presetter": "^3.0.0",
|
||||
"rollup": "^2.0.0",
|
||||
"rollup-plugin-postcss": "^4.0.0",
|
||||
"rollup-plugin-ts": "^1.0.0",
|
||||
"rollup-plugin-tsconfig-paths": "^1.0.0",
|
||||
"rollup-plugin-visualizer": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"presetter": "file:../presetter",
|
||||
"presetter-preset": "file:../preset"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* *** MIT LICENSE ***
|
||||
* -------------------------------------------------------------------------
|
||||
* This code may be modified and distributed under the MIT license.
|
||||
* See the LICENSE file for details.
|
||||
* -------------------------------------------------------------------------
|
||||
*
|
||||
* @summary Collection of preset assets for bundling a project with rollup
|
||||
*
|
||||
* @author Alvis HT Tang <alvis@hilbert.space>
|
||||
* @license MIT
|
||||
* @copyright Copyright (c) 2021 - All Rights Reserved.
|
||||
* -------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { resolve } from 'path';
|
||||
import { loadFile, template } from 'presetter';
|
||||
|
||||
import { getRollupParameter } from './rollup';
|
||||
|
||||
import type { PresetAsset } from 'presetter';
|
||||
|
||||
import type { RollupConfig } from './rollup';
|
||||
|
||||
// paths to the template directory
|
||||
const TEMPLATES = resolve(__dirname, '..', 'templates');
|
||||
const CONFIGS = resolve(__dirname, '..', 'configs');
|
||||
|
||||
/** config for this preset */
|
||||
export type PresetConfig = {
|
||||
rollup?: RollupConfig;
|
||||
};
|
||||
|
||||
/** List of configurable variables */
|
||||
export type Variable = {
|
||||
/** the directory containing all source code (default: source) */
|
||||
source: string;
|
||||
/** the directory containing all the compiled files (default: lib) */
|
||||
output: string;
|
||||
};
|
||||
|
||||
export const DEFAULT_VARIABLE: Variable = {
|
||||
source: 'source',
|
||||
output: 'lib',
|
||||
};
|
||||
|
||||
/**
|
||||
* get the list of templates provided by this preset
|
||||
* @returns list of preset templates
|
||||
*/
|
||||
export default async function (): Promise<PresetAsset> {
|
||||
return {
|
||||
template: {
|
||||
'rollup.config.ts': async (context) => {
|
||||
const content = await loadFile(
|
||||
resolve(TEMPLATES, 'rollup.config.ts'),
|
||||
'text',
|
||||
);
|
||||
const variable = await getRollupParameter(context);
|
||||
|
||||
return template(content, variable);
|
||||
},
|
||||
},
|
||||
scripts: resolve(TEMPLATES, 'scripts.yaml'),
|
||||
noSymlinks: ['rollup.config.ts'],
|
||||
supplementaryConfig: {
|
||||
gitignore: ['/rollup.config.ts'],
|
||||
rollup: resolve(CONFIGS, 'rollup.yaml'),
|
||||
},
|
||||
variable: DEFAULT_VARIABLE,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* *** MIT LICENSE ***
|
||||
* -------------------------------------------------------------------------
|
||||
* This code may be modified and distributed under the MIT license.
|
||||
* See the LICENSE file for details.
|
||||
* -------------------------------------------------------------------------
|
||||
*
|
||||
* @summary Collection of plugin related helpers
|
||||
*
|
||||
* @author Alvis HT Tang <alvis@hilbert.space>
|
||||
* @license MIT
|
||||
* @copyright Copyright (c) 2021 - All Rights Reserved.
|
||||
* -------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { isDirective } from 'presetter';
|
||||
|
||||
import type { ApplyDirective, ImportDirective } from 'presetter';
|
||||
|
||||
/** full configuration about a plugin */
|
||||
export type PluginConfiguration =
|
||||
| [name: PluginHeader]
|
||||
| [name: PluginHeader, options: PluginOptions | null];
|
||||
|
||||
/** specification of a plugin name and its handling direction (e.g. by invoking the function or just simply specify the name) */
|
||||
export type PluginHeader = string | ApplyDirective;
|
||||
|
||||
/** options for a plugin */
|
||||
export type PluginOptions =
|
||||
| Record<string, unknown>
|
||||
| ApplyDirective
|
||||
| ImportDirective;
|
||||
|
||||
/** plugin configuration as an object */
|
||||
export type PluginObject = Record<PluginHeader, PluginOptions | null>;
|
||||
|
||||
/** plugin configuration as an array */
|
||||
export type PluginList = PluginListItem[];
|
||||
|
||||
/** possible types for individual item in a PluginList */
|
||||
type PluginListItem = PluginHeader | [name: PluginHeader] | PluginConfiguration;
|
||||
|
||||
/** all possible configuration form for a collection of plugins */
|
||||
export type PluginManifest = PluginList | PluginObject;
|
||||
|
||||
/**
|
||||
* ensure that the given value is a valid PluginManifest
|
||||
* @param value value to be tested
|
||||
* @returns nothing if it's a pass
|
||||
*/
|
||||
export function assertPluginManifest(
|
||||
value: unknown,
|
||||
): asserts value is PluginManifest {
|
||||
if (typeof value === 'object') {
|
||||
if (Array.isArray(value)) {
|
||||
return assertPluginList(value);
|
||||
} else if (value !== null) {
|
||||
return assertPluginObject(value as Record<string, unknown>);
|
||||
}
|
||||
}
|
||||
|
||||
throw new TypeError('plugin manifest is not in a supported format');
|
||||
}
|
||||
|
||||
/**
|
||||
* ensure that the given value is a valid PluginObject
|
||||
* @param value value to be tested
|
||||
*/
|
||||
export function assertPluginObject(
|
||||
value: Record<string, unknown>,
|
||||
): asserts value is PluginObject {
|
||||
// all values must be an object
|
||||
if (
|
||||
[...Object.values(value)].some(
|
||||
(opt) => typeof opt !== 'object' && !isDirective(opt),
|
||||
)
|
||||
) {
|
||||
throw new TypeError('all plugin options must be a object');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ensure that the given value is a valid PluginList
|
||||
* @param value value to be tested
|
||||
*/
|
||||
export function assertPluginList(
|
||||
value: unknown[],
|
||||
): asserts value is PluginList {
|
||||
for (const plugin of value) {
|
||||
assertPluginListItem(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
const PLUGIN_LIST_MAX_ITEMS = 2;
|
||||
|
||||
/**
|
||||
* ensure that the given value is a valid PluginListItem
|
||||
* @param value value to be tested
|
||||
*/
|
||||
export function assertPluginListItem(
|
||||
value: unknown,
|
||||
): asserts value is PluginListItem {
|
||||
if (
|
||||
typeof value !== 'string' &&
|
||||
!(
|
||||
Array.isArray(value) &&
|
||||
value.length <= PLUGIN_LIST_MAX_ITEMS &&
|
||||
typeof value[0] === 'string' &&
|
||||
(isDirective(value[1]) ||
|
||||
['undefined', 'object'].includes(typeof value[1]))
|
||||
)
|
||||
) {
|
||||
throw new TypeError(
|
||||
'a plugin manifest in an array form must be in either one of the following forms: string, [string], [string, object]',
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* *** MIT LICENSE ***
|
||||
* -------------------------------------------------------------------------
|
||||
* This code may be modified and distributed under the MIT license.
|
||||
* See the LICENSE file for details.
|
||||
* -------------------------------------------------------------------------
|
||||
*
|
||||
* @summary Collection of helpers for rollup
|
||||
*
|
||||
* @author Alvis HT Tang <alvis@hilbert.space>
|
||||
* @license MIT
|
||||
* @copyright Copyright (c) 2021 - All Rights Reserved.
|
||||
* -------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import {
|
||||
isDirective,
|
||||
isJSON,
|
||||
merge,
|
||||
resolveDirective,
|
||||
template,
|
||||
} from 'presetter';
|
||||
|
||||
import { assertPluginManifest } from './plugin';
|
||||
|
||||
import type {
|
||||
PluginConfiguration,
|
||||
PluginList,
|
||||
PluginManifest,
|
||||
PluginObject,
|
||||
} from './plugin';
|
||||
import type {
|
||||
ApplyDirective,
|
||||
ImportDirective,
|
||||
ResolvedPresetContext,
|
||||
} from 'presetter';
|
||||
|
||||
|
||||
/** preset configuration for rollup */
|
||||
export interface RollupConfig {
|
||||
[index: string]: unknown | RollupConfig;
|
||||
/** list of plugin and its options */
|
||||
plugins?: PluginManifest | ApplyDirective | ImportDirective;
|
||||
}
|
||||
|
||||
/** genuine configuration that rollup would take, making sure all plugins are a list */
|
||||
interface TrueRollupConfig {
|
||||
[index: string]: unknown | TrueRollupConfig;
|
||||
/** list of plugin and its options */
|
||||
plugins?: PluginConfiguration[];
|
||||
}
|
||||
|
||||
/** transformed configuration for rollup, with all plugins represented by an object */
|
||||
interface IntermediateRollupConfig {
|
||||
[index: string]: unknown | IntermediateRollupConfig;
|
||||
/** list of plugin and its options */
|
||||
plugins?: PluginObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* get template parameters for rollup
|
||||
* @param context context about the build environment
|
||||
* @returns template parameter related to rollup
|
||||
*/
|
||||
export async function getRollupParameter(
|
||||
context: ResolvedPresetContext,
|
||||
): Promise<Record<'rollupImport' | 'rollupExport', string>> {
|
||||
const { config, variable } = context.custom;
|
||||
|
||||
const normalisedConfig = template(
|
||||
normaliseConfig(transformConfig({ ...config.rollup })),
|
||||
variable,
|
||||
);
|
||||
|
||||
return generateRollupParameter(normalisedConfig, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* generate template parameters for rollup
|
||||
* @param config normalised rollup config
|
||||
* @param context context about the build environment
|
||||
* @returns template parameter related to rollup
|
||||
*/
|
||||
function generateRollupParameter(
|
||||
config: TrueRollupConfig,
|
||||
context: ResolvedPresetContext,
|
||||
): Record<'rollupImport' | 'rollupExport', string> {
|
||||
const { importMap, stringifiedConfig } = resolveDirective(config, context);
|
||||
|
||||
// generate import statements
|
||||
const rollupImport = Object.entries(importMap)
|
||||
.map(([name, resolved]) => `import * as ${resolved} from '${name}';`)
|
||||
.join('\n');
|
||||
|
||||
// generate export statements
|
||||
const rollupExport = `export default ${stringifiedConfig}`;
|
||||
|
||||
return { rollupImport, rollupExport };
|
||||
}
|
||||
|
||||
/**
|
||||
* normalise rollup config with all plugins represented as a list
|
||||
* @param config transformed config
|
||||
* @returns config that rollup would take
|
||||
*/
|
||||
function normaliseConfig(config: IntermediateRollupConfig): TrueRollupConfig {
|
||||
return Object.fromEntries(
|
||||
Object.entries(config).map(([key, value]): [string, unknown] => {
|
||||
return [
|
||||
key,
|
||||
isDirective(value) ? value : normaliseConfigValue(key, value),
|
||||
];
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* try to normalise any nested configuration
|
||||
* @param key field name
|
||||
* @param value value of a field
|
||||
* @returns normalised value
|
||||
*/
|
||||
function normaliseConfigValue(key: string, value: unknown): unknown {
|
||||
switch (key) {
|
||||
case 'plugins':
|
||||
return [
|
||||
...Object.entries(value as PluginObject)
|
||||
.filter(([_, options]) => options !== null)
|
||||
.map(([plugin, options]) =>
|
||||
[plugin, normaliseConfigValue(plugin, options)].filter(
|
||||
(element) => element !== undefined,
|
||||
),
|
||||
),
|
||||
];
|
||||
default:
|
||||
return isJSON(value)
|
||||
? normaliseConfig(value as IntermediateRollupConfig)
|
||||
: value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* transform rollup config with plugins represented by an object for better merging
|
||||
* @param config rollup config in .presetterrc
|
||||
* @returns transformed config
|
||||
*/
|
||||
function transformConfig(
|
||||
config: Record<string, any>,
|
||||
): IntermediateRollupConfig {
|
||||
return Object.fromEntries(
|
||||
Object.entries(config).map(([key, value]): [string, unknown] => {
|
||||
return [
|
||||
key,
|
||||
isDirective(value) ? value : transformConfigValue(key, value),
|
||||
];
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* try to transform any nested configuration
|
||||
* @param key field name
|
||||
* @param value value of a field
|
||||
* @returns transformed value
|
||||
*/
|
||||
function transformConfigValue(key: string, value: unknown): unknown {
|
||||
switch (key) {
|
||||
case 'plugins':
|
||||
assertPluginManifest(value);
|
||||
|
||||
return objectifyPlugins(value);
|
||||
|
||||
default:
|
||||
return isJSON(value) ? transformConfig(value) : value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* objectify rollup plugins
|
||||
* @param plugins rollup plugin config
|
||||
* @returns normalised plugin config
|
||||
*/
|
||||
function objectifyPlugins(
|
||||
plugins: PluginManifest,
|
||||
): IntermediateRollupConfig['plugins'] {
|
||||
const normalisedPlugin: PluginObject = {};
|
||||
|
||||
const pluginList: PluginConfiguration[] = Array.isArray(plugins)
|
||||
? arrayToPluginConfiguration(plugins)
|
||||
: objectToPluginConfiguration(plugins);
|
||||
|
||||
for (const [name, options] of pluginList) {
|
||||
Object.assign(
|
||||
normalisedPlugin,
|
||||
merge(normalisedPlugin, { [name]: options }),
|
||||
);
|
||||
}
|
||||
|
||||
return normalisedPlugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* normalise rollup plugin config in array form
|
||||
* @param plugins rollup plugin config in array form
|
||||
* @returns normalised plugin config
|
||||
*/
|
||||
function arrayToPluginConfiguration(
|
||||
plugins: PluginList,
|
||||
): PluginConfiguration[] {
|
||||
return plugins.map((plugin) =>
|
||||
typeof plugin === 'string' ? [plugin] : plugin,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* normalise rollup plugin config in object form
|
||||
* @param plugins rollup plugin config in object form
|
||||
* @returns normalised plugin config
|
||||
*/
|
||||
function objectToPluginConfiguration(
|
||||
plugins: PluginObject,
|
||||
): PluginConfiguration[] {
|
||||
return [...Object.entries(plugins)];
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* *** MIT LICENSE ***
|
||||
* -------------------------------------------------------------------------
|
||||
* This code may be modified and distributed under the MIT license.
|
||||
* See the LICENSE file for details.
|
||||
* -------------------------------------------------------------------------
|
||||
*
|
||||
* @summary Tests on config generation
|
||||
*
|
||||
* @author Alvis HT Tang <alvis@hilbert.space>
|
||||
* @license MIT
|
||||
* @copyright Copyright (c) 2020 - All Rights Reserved.
|
||||
* -------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { readdirSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { resolveContext, resolveDynamicMap } from 'presetter';
|
||||
|
||||
import getPresetAsset from '#index';
|
||||
|
||||
jest.mock('path', () => ({
|
||||
__esModule: true,
|
||||
...jest.requireActual('path'),
|
||||
resolve: jest.fn(jest.requireActual('path').resolve),
|
||||
}));
|
||||
|
||||
describe('fn:getPresetAsset', () => {
|
||||
it('use all templates', async () => {
|
||||
const assets = [await getPresetAsset()];
|
||||
const context = await resolveContext(assets, {
|
||||
target: { name: 'preset', root: '/', package: {} },
|
||||
custom: { preset: 'preset' },
|
||||
});
|
||||
|
||||
// load all potential dynamic content
|
||||
await resolveDynamicMap(assets, context, 'supplementaryConfig');
|
||||
await resolveDynamicMap(assets, context, 'template');
|
||||
|
||||
const TEMPLATES = resolve(__dirname, '..', 'templates');
|
||||
const allTemplates = await readdirSync(TEMPLATES);
|
||||
const CONFIGS = resolve(__dirname, '..', 'configs');
|
||||
const supplementaryConfig = await readdirSync(CONFIGS);
|
||||
|
||||
for (const path of allTemplates) {
|
||||
expect(resolve).toBeCalledWith(TEMPLATES, path);
|
||||
}
|
||||
for (const path of supplementaryConfig) {
|
||||
expect(resolve).toBeCalledWith(CONFIGS, path);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* *** MIT LICENSE ***
|
||||
* -------------------------------------------------------------------------
|
||||
* This code may be modified and distributed under the MIT license.
|
||||
* See the LICENSE file for details.
|
||||
* -------------------------------------------------------------------------
|
||||
*
|
||||
* @summary Tests on plugin related helpers
|
||||
*
|
||||
* @author Alvis HT Tang <alvis@hilbert.space>
|
||||
* @license MIT
|
||||
* @copyright Copyright (c) 2021 - All Rights Reserved.
|
||||
* -------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import {
|
||||
assertPluginList,
|
||||
assertPluginListItem,
|
||||
assertPluginObject,
|
||||
assertPluginManifest,
|
||||
} from '#plugin';
|
||||
|
||||
describe('fn:assertPluginListItem', () => {
|
||||
it('pass with just a string', () => {
|
||||
expect(() => assertPluginListItem('plugin')).not.toThrow();
|
||||
});
|
||||
|
||||
it('pass with a string in an array', () => {
|
||||
expect(() => assertPluginListItem(['plugin'])).not.toThrow();
|
||||
});
|
||||
|
||||
it('pass with a string and its options in an array', () => {
|
||||
expect(() =>
|
||||
assertPluginListItem(['plugin', { options: true }]),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('fails with a non-string header', () => {
|
||||
expect(() => assertPluginListItem([0])).toThrow(TypeError);
|
||||
});
|
||||
|
||||
it('fails with an array more than 2 items', () => {
|
||||
expect(() =>
|
||||
assertPluginListItem(['plugin', { options: true }, 'extra']),
|
||||
).toThrow(TypeError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fn:assertPluginList', () => {
|
||||
it('pass with a valid plugin configuration list', () => {
|
||||
expect(() =>
|
||||
assertPluginList(['plugin', ['plugin'], ['plugin', { options: true }]]),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('fail with any invalid plugin configurations', () => {
|
||||
expect(() =>
|
||||
assertPluginList([
|
||||
'plugin',
|
||||
['plugin'],
|
||||
['plugin', { options: true }],
|
||||
{ invalid: true },
|
||||
]),
|
||||
).toThrow(TypeError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fn:assertPluginObject', () => {
|
||||
it('pass with a valid plugin configuration object', () => {
|
||||
expect(() =>
|
||||
assertPluginObject({ plugin: { options: true } }),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('fail with any invalid plugin options', () => {
|
||||
expect(() => assertPluginObject({ plugin: true })).toThrow(TypeError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fn:assertPluginManifest', () => {
|
||||
it('pass with a valid plugin configuration object', () => {
|
||||
expect(() =>
|
||||
assertPluginManifest({ plugin: { options: true } }),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('pass with a valid plugin configuration list', () => {
|
||||
expect(() =>
|
||||
assertPluginManifest([
|
||||
'plugin',
|
||||
['plugin'],
|
||||
['plugin', { options: true }],
|
||||
]),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('fail with any invalid manifest', () => {
|
||||
expect(() => assertPluginManifest(null)).toThrow(TypeError);
|
||||
expect(() => assertPluginManifest('invalid')).toThrow(TypeError);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,220 @@
|
|||
/*
|
||||
* *** MIT LICENSE ***
|
||||
* -------------------------------------------------------------------------
|
||||
* This code may be modified and distributed under the MIT license.
|
||||
* See the LICENSE file for details.
|
||||
* -------------------------------------------------------------------------
|
||||
*
|
||||
* @summary Tests on the helpers for rollup
|
||||
*
|
||||
* @author Alvis HT Tang <alvis@hilbert.space>
|
||||
* @license MIT
|
||||
* @copyright Copyright (c) 2021 - All Rights Reserved.
|
||||
* -------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { getRollupParameter } from '#rollup';
|
||||
|
||||
import type { Config, ResolvedPresetContext } from 'presetter';
|
||||
|
||||
describe('fn:getRollupParameter', () => {
|
||||
const generateContext = (config?: Config): ResolvedPresetContext => ({
|
||||
target: { name: 'target', root: '/path/to/target', package: {} },
|
||||
custom: {
|
||||
preset: 'preset',
|
||||
config: config ? { rollup: config } : {},
|
||||
noSymlinks: [],
|
||||
variable: {},
|
||||
},
|
||||
});
|
||||
|
||||
it('add plugins by importing from another config files', async () => {
|
||||
expect(
|
||||
await getRollupParameter(
|
||||
generateContext({
|
||||
plugins: '@import config[plugins]',
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
rollupImport: `import * as import0 from 'config';`,
|
||||
rollupExport: 'export default {"plugins": import0.plugins}',
|
||||
});
|
||||
});
|
||||
|
||||
it('add a plugin by adding the plugin in the object form, using the supplied options', async () => {
|
||||
expect(
|
||||
await getRollupParameter(
|
||||
generateContext({
|
||||
plugins: { '@apply newPlugin': { name: 'newPlugin' } },
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
rollupImport: `import * as import0 from 'newPlugin';`,
|
||||
rollupExport:
|
||||
'export default {"plugins": [import0(...[{"name": "newPlugin"}])]}',
|
||||
});
|
||||
});
|
||||
|
||||
it('add a plugin by just the plugin name, using everything default', async () => {
|
||||
expect(
|
||||
await getRollupParameter(
|
||||
generateContext({
|
||||
plugins: ['@apply newPlugin'],
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
rollupImport: `import * as import0 from 'newPlugin';`,
|
||||
rollupExport: 'export default {"plugins": [import0(...[])]}',
|
||||
});
|
||||
});
|
||||
|
||||
it('add a plugin by adding the plugin in the array form, using everything default', async () => {
|
||||
expect(
|
||||
await getRollupParameter(
|
||||
generateContext({
|
||||
plugins: [['@apply newPlugin']],
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
rollupImport: `import * as import0 from 'newPlugin';`,
|
||||
rollupExport: 'export default {"plugins": [import0(...[])]}',
|
||||
});
|
||||
});
|
||||
|
||||
it('add a plugin by adding the plugin in the array form, using the supplied options', async () => {
|
||||
expect(
|
||||
await getRollupParameter(
|
||||
generateContext({
|
||||
plugins: [['@apply newPlugin', { name: 'newPlugin' }]],
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
rollupImport: `import * as import0 from 'newPlugin';`,
|
||||
rollupExport:
|
||||
'export default {"plugins": [import0(...[{"name": "newPlugin"}])]}',
|
||||
});
|
||||
});
|
||||
|
||||
it('remove a plugin by setting the plugin config as null', async () => {
|
||||
expect(
|
||||
await getRollupParameter(
|
||||
generateContext({
|
||||
plugins: {
|
||||
'@apply pluginWithOptions': null,
|
||||
},
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
rollupImport: ``,
|
||||
rollupExport: 'export default {"plugins": []}',
|
||||
});
|
||||
});
|
||||
|
||||
it('add a plugin from a named import', async () => {
|
||||
expect(
|
||||
await getRollupParameter(
|
||||
generateContext({
|
||||
plugins: {
|
||||
'@apply pluginWithOptions': null,
|
||||
'@apply pluginWithoutOptions': null,
|
||||
'@apply newPlugin[plugin]': { options: true },
|
||||
},
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
rollupImport: `import * as import0 from 'newPlugin';`,
|
||||
rollupExport:
|
||||
'export default {"plugins": [import0.plugin(...[{"options": true}])]}',
|
||||
});
|
||||
});
|
||||
|
||||
it('generate default parameters if no further config is given', async () => {
|
||||
expect(await getRollupParameter(generateContext())).toEqual({
|
||||
rollupImport: ``,
|
||||
rollupExport: 'export default {}',
|
||||
});
|
||||
});
|
||||
|
||||
it('generate config with extra options other than plugins', async () => {
|
||||
expect(
|
||||
await getRollupParameter(
|
||||
generateContext({
|
||||
cache: null,
|
||||
extra: { options: true },
|
||||
external: ['import1', 'import2'],
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
rollupImport: ``,
|
||||
rollupExport:
|
||||
'export default {"cache": null, "extra": {"options": true}, "external": ["import1", "import2"]}',
|
||||
});
|
||||
});
|
||||
|
||||
it('generate extra import statements for imports within plugin options', async () => {
|
||||
expect(
|
||||
await getRollupParameter(
|
||||
generateContext({
|
||||
plugins: {
|
||||
'@apply pluginWithOptions': '@import another',
|
||||
'@apply pluginWithoutOptions': '@import another',
|
||||
},
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
rollupImport: `import * as import0 from 'another';\nimport * as import1 from 'pluginWithOptions';\nimport * as import2 from 'pluginWithoutOptions';`,
|
||||
rollupExport: `export default {"plugins": [import1(...[import0]), import2(...[import0])]}`,
|
||||
});
|
||||
|
||||
expect(
|
||||
await getRollupParameter(
|
||||
generateContext({
|
||||
plugins: [
|
||||
['@apply pluginWithOptions', '@import another'],
|
||||
['@apply pluginWithoutOptions', '@import another'],
|
||||
],
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
rollupImport: `import * as import0 from 'another';\nimport * as import1 from 'pluginWithOptions';\nimport * as import2 from 'pluginWithoutOptions';`,
|
||||
rollupExport: `export default {"plugins": [import1(...[import0]), import2(...[import0])]}`,
|
||||
});
|
||||
});
|
||||
|
||||
it('generate only one import statement per unique import', async () => {
|
||||
expect(
|
||||
await getRollupParameter(
|
||||
generateContext({
|
||||
plugins: {
|
||||
'@apply pluginWithOptions': null,
|
||||
'@apply pluginWithoutOptions': null,
|
||||
'@apply plugin0': '@import another[export0]',
|
||||
'@apply plugin1': '@import another[export1]',
|
||||
},
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
rollupImport: `import * as import0 from 'another';\nimport * as import1 from 'plugin0';\nimport * as import2 from 'plugin1';`,
|
||||
rollupExport: `export default {"plugins": [import1(...[import0.export0]), import2(...[import0.export1])]}`,
|
||||
});
|
||||
});
|
||||
|
||||
it('support nested plugin declaration', async () => {
|
||||
expect(
|
||||
await getRollupParameter(
|
||||
generateContext({
|
||||
plugins: {
|
||||
'@apply pluginWithOptions': null,
|
||||
'@apply pluginWithoutOptions': null,
|
||||
'@apply plugin0': {
|
||||
plugins: { another: '@import options[export0]' },
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
rollupImport: `import * as import0 from 'options';\nimport * as import1 from 'plugin0';`,
|
||||
rollupExport: `export default {"plugins": [import1(...[{"plugins": [["another", import0.export0]]}])]}`,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
{rollupImport}
|
||||
|
||||
{rollupExport}
|
|
@ -0,0 +1,5 @@
|
|||
# replace the `prepare` template from presetter-preset
|
||||
# so that the build procedure will not be triggered upon package installation
|
||||
build: run-s clean build:rollup
|
||||
build:rollup: cross-env NODE_ENV=production rollup --config rollup.config.ts --configPlugin rollup-plugin-ts
|
||||
develop: run-s "build:rollup -- --watch {@}" --
|
Loading…
Reference in New Issue