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

chore: add @rollup/plugin-html #57

Merged
merged 10 commits into from
Nov 29, 2019
10 changes: 5 additions & 5 deletions packages/auto-install/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ npm install @rollup/plugin-inject --save-dev
Create a `rollup.config.js` [configuration file](https://www.rollupjs.org/guide/en/#configuration-files) and import the plugin:

```js
import auto from "@rollup/plugin-auto-install";
import resolve from "rollup-plugin-node-resolve";
import auto from '@rollup/plugin-auto-install';
import resolve from 'rollup-plugin-node-resolve';

export default {
input: "src/index.js",
input: 'src/index.js',
output: {
dir: "output",
format: "cjs"
dir: 'output',
format: 'cjs'
},
plugins: [auto(), resolve()]
};
Expand Down
136 changes: 136 additions & 0 deletions packages/html/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
[npm]: https://img.shields.io/npm/v/@rollup/plugin-html
[npm-url]: https://www.npmjs.com/package/@rollup/plugin-html
[size]: https://packagephobia.now.sh/badge?p=@rollup/plugin-html
[size-url]: https://packagephobia.now.sh/result?p=@rollup/plugin-html

[![npm][npm]][npm-url]
[![size][size]][size-url]
[![libera manifesto](https://img.shields.io/badge/libera-manifesto-lightgrey.svg)](https://liberamanifesto.com)

# @rollup/plugin-html

🍣 A Rollup plugin which creates HTML files to serve Rollup bundles

## Requirements

This plugin requires an [LTS](https://github.com/nodejs/Release) Node version (v8.0.0+) and Rollup v1.20.0+.

## Install

Using npm:

```console
npm install @rollup/plugin-html --save-dev
```

## Usage

Create a `rollup.config.js` [configuration file](https://www.rollupjs.org/guide/en/#configuration-files) and import the plugin:

```js
const html = require('@rollup/plugin-html');

module.exports = {
input: 'src/index.js',
output: {
dir: 'output',
format: 'cjs'
},
plugins: [html()]
};
```

Then call `rollup` either via the [CLI](https://www.rollupjs.org/guide/en/#command-line-reference) or the [API](https://www.rollupjs.org/guide/en/#javascript-api).

Once run successfully, an HTML file should be written to the bundle output destination.

## Options

### `attributes`

Type: `Object`<br>
Default: `{ html: { lang: 'en' }, link: null, script: null }`

Specifies additional attributes for `html`, `link`, and `script` elements. For each property, provide an object with key-value pairs that represent an HTML element attribute name and value. By default, the `html` element is rendered with an attribute of `lang="en"`.

### `fileName`

Type: `String`<br>
Default: `'index.html'`

Specifies the name of the HTML to emit.

### `publicPath`

Type: `String`<br>
Default: `''`

Specifies a path to prepend to all bundle assets (files) in the HTML output.

### `template`

Type: `Fumction`<br>
Default: `internal function`
Returns: `String`

Specifies a function that provides the rendered source for the HTML output. The function should be in the form of:

```js
const template = ({ attributes, files, publicPath, title }) => { ... }
```

- `attributes`: corresponds to the `attributes` option passed to the plugin
- `files`: An `Array` of `String` containing the assets (files) in the bundle that will be emitted
- `publicPath`: corresponds to the `publicPath` option passed to the plugin
- `title`: corresponds to the `title` option passed to the plugin

By default this is handled internally and produces HTML in the following format:

```html
<!DOCTYPE html>
<html ${attributes}>
<head>
<meta charset="utf-8" />
<title>${title}</title>
${links}
</head>
<body>
${scripts}
</body>
</html>
```

Where `${links}` represents all `<link ..` tags for CSS and `${scripts}` represents all `<script...` tags for JavaScript files.

### `title`

Type: `String`<br>
Default: `'Rollup Bundle'`

Specifies the HTML document title.

## Exports

### `makeHtmlAttributes(attributes)`

Parameters: `attributes`, Type: `Object`<br>
Returns: `String`

Consumes an object with key-value pairs that represent an HTML element attribute name and value. The function returns all pairs as a space-separated string of valid HTML element attributes. e.g.

```js
const { makeHtmlAttributes } = require('@rollup/plugin-html');

makeHtmlAttributes({ lang: 'en', 'data-batcave': 'secret' });
// -> 'lang="en" data-batcave="secret"'
```

## Attribution

This plugin was inspired by and is based upon [mini-html-webpack-plugin](https://github.com/styleguidist/mini-html-webpack-plugin) by Juho Vepsalainen, with permission.

## Meta

[CONTRIBUTING](/.github/CONTRIBUTING.md)

[LICENSE (MIT)](/LICENSE)
87 changes: 87 additions & 0 deletions packages/html/lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
const { extname } = require('path');

const getFiles = (bundle) => {
Copy link
Member

Choose a reason for hiding this comment

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

This function should ONLY get entry point files (isEntry: true) and ignore the rest. Reason:

  • For IIFE and UMD formats, there will only be a single entry point anyway
  • All other formats import their dependencies themselves. If you add all files as script tags, you will at least destroy dynamic lazy loading of chunks (if it works at all).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Disagree here. Various assets can be added to a bundle's files via plugins, and we should pick those up. CSS is the major example here.

Copy link
Member

Choose a reason for hiding this comment

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

This function puts all files .js files into an array and throws away all meta information. It is virtually impossible to distiguish the auto-generated chunks from the entry points. This makes the plugin rather useless for code-splitting IMO. Either you provide some meta information, or you ignore the non-entry chunks. I would be very confused if a CSS plugin was broken if you ignored the auto-generated chunks, as they are still imported by the main JavaScript. And maybe this even breaks CSS plugins in so far as maybe their chunks are meant to be loaded dynamically together with their content while you make them load up-front instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That makes the case for providing the bundle metadata to the template option function for complex builds that the default template doesn't suit.

Copy link
Member

Choose a reason for hiding this comment

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

That would be sufficient

Copy link
Member

Choose a reason for hiding this comment

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

With regard to my initial comment, maybe I mis-stated what I mean: This function should of course return all assets, but with regard to chunks, it should only return entry chunks, as all non-entry chunks (read: .js files) will be loaded automatically by the entry chunks, no additional work necessary. But additionally having the bundle available to the template still would not hurt.

const fileNames = Object.values(bundle).map(({ fileName }) => fileName);
const files = {};
for (const fileName of fileNames) {
const extension = extname(fileName).substring(1);
files[extension] = (files[extension] || []).concat(fileName);
}

return files;
};

const makeHtmlAttributes = (attributes) => {
if (!attributes) {
return '';
}

const keys = Object.keys(attributes);
// eslint-disable-next-line no-param-reassign
return keys.reduce((result, key) => (result += ` ${key}="${attributes[key]}"`), '');
};

const defaultTemplate = async ({ attributes, files, publicPath, title }) => {
const scripts = (files.js || [])
.map((fileName) => {
const attrs = makeHtmlAttributes(attributes.script);
return `<script src="${publicPath}${fileName}"${attrs}></script>`;
})
.join('\n');

const links = (files.css || [])
.map((fileName) => {
const attrs = makeHtmlAttributes(attributes.link);
return `<link href="${publicPath}${fileName}" rel="stylesheet"${attrs}>`;
})
.join('\n');

return `
<!doctype html>
<html${makeHtmlAttributes(attributes.html)}>
<head>
<meta charset="utf-8">
<title>${title}</title>
${links}
</head>
<body>
${scripts}
</body>
</html>`;
};

const defaults = {
attributes: {
link: null,
html: { lang: 'en' },
script: null
},
fileName: 'index.html',
publicPath: '',
template: defaultTemplate,
title: 'Rollup Bundle'
};

const html = (opts) => {
const { attributes, fileName, publicPath, template, title } = Object.assign({}, defaults, opts);
return {
name: 'html',

async generateBundle(output, bundle) {
const files = getFiles(bundle);
const source = await template({ attributes, files, publicPath, title });

const htmlFile = {
type: 'asset',
source,
name: 'Rollup HTML Asset',
Copy link
Member

Choose a reason for hiding this comment

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

The name will be ignored as a fileName is provided.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Noted. We need to update the Rollup docs as this is a copy/paste/modify from them. I'll see about doing that this week.

Copy link
Member

Choose a reason for hiding this comment

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

It is written in the first paragraph after the code block here: https://rollupjs.org/guide/en/#thisemitfileemittedfile-emittedchunk--emittedasset--string

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I did read that, but with the code sample above that having both and both being optional, I had assumed it would be prudent to set name in the event that something was inspecting it down the line. Some extra verbiage there could be useful to point out there's no need to set the other property.

Copy link
Member

Choose a reason for hiding this comment

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

True. It does not hurt adding the name, it is just ignored.

fileName
};

this.emitFile(htmlFile);
}
};
};

module.exports = html;
module.exports.makeHtmlAttributes = makeHtmlAttributes;
57 changes: 57 additions & 0 deletions packages/html/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"name": "@rollup/plugin-html",
"version": "0.1.0",
"publishConfig": {
"access": "public"
},
"description": "Creates HTML files to serve Rollup bundles",
"license": "MIT",
"repository": "rollup/plugins",
"author": "Andrew Powell <[email protected]>",
"homepage": "https://github.com/rollup/plugins/packages/beep",
"bugs": "https://github.com/rollup/plugins/issues",
"main": "lib/index.js",
"engines": {
"node": ">= 8.0.0"
},
"scripts": {
"ci:coverage": "nyc pnpm run test && nyc report --reporter=text-lcov > coverage.lcov",
"ci:lint": "pnpm run lint && pnpm run security",
"ci:lint:commits": "commitlint --from=${CIRCLE_BRANCH} --to=${CIRCLE_SHA1}",
"ci:test": "pnpm run test -- --verbose",
"lint": "pnpm run lint:js && pnpm run lint:docs && pnpm run lint:package",
"lint:docs": "prettier --single-quote --write *.md",
"lint:js": "eslint --fix --cache lib test",
"lint:package": "prettier --write package.json --plugin=prettier-plugin-package",
"prepublishOnly": "pnpm run lint",
"security": "echo 'pnpm needs `npm audit` support'",
"test": "ava"
},
"files": [
"lib",
"README.md",
"LICENSE"
],
"keywords": [
"rollup",
"plugin",
"html",
"template"
],
"peerDependencies": {
"rollup": "^1.20.0"
},
"devDependencies": {
"del": "^5.1.0",
"rollup": "^1.27.5",
"rollup-plugin-postcss": "^2.0.3"
},
"ava": {
"files": [
"!**/fixtures/**",
"!**/helpers/**",
"!**/recipes/**",
"!**/types.ts"
]
}
}
16 changes: 16 additions & 0 deletions packages/html/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const css = require('rollup-plugin-postcss');

const pkg = require('./package.json');

const dependencies = Object.keys(pkg.dependencies || {});

const html = require('.');

export default [
{
input: ['test/fixtures/joker.js'],
output: { dir: 'dist', format: 'cjs' },
external: [...dependencies],
plugins: [css({ extract: true }), html()]
}
];
Empty file.
1 change: 1 addition & 0 deletions packages/html/test/fixtures/joker.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* { width: 100%; }
2 changes: 2 additions & 0 deletions packages/html/test/fixtures/joker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line
import style from './joker.css';
2 changes: 2 additions & 0 deletions packages/html/test/fixtures/robin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line
import * as batman from './batman.js';
Loading