mirror of
https://github.com/jlengrand/open-wc.git
synced 2026-03-10 08:31:19 +00:00
feat(building-rollup): added building-rollup
This commit is contained in:
committed by
Thomas Allmer
parent
5ba8e3f4b0
commit
273cb948fe
@@ -1,4 +1,4 @@
|
||||
node_modules
|
||||
coverage/
|
||||
/packages/generator-open-wc/generators/*/templates/**/*
|
||||
dist
|
||||
dist
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -27,4 +27,5 @@ package-lock.json
|
||||
|
||||
## build output
|
||||
dist
|
||||
build-stats.json
|
||||
build-stats.json
|
||||
stats.html
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
node_modules
|
||||
coverage/
|
||||
/packages/generator-open-wc/generators/*/templates/**/*
|
||||
dist
|
||||
dist
|
||||
|
||||
@@ -44,6 +44,7 @@ const sidebar = [
|
||||
children: [
|
||||
['/building/', 'Getting started'],
|
||||
'/building/building-webpack',
|
||||
'/building/building-rollup',
|
||||
'/building/polyfills-loader',
|
||||
],
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ Our recommended setups address the first four points but refrain from introducin
|
||||
[Webpack](https://webpack.js.org/) is a popular and flexible build tool, with a growing ecosystem of plugins. We recommend it as a general purpose tool. See our [default configuration](/building/building-webpack.html) to get started.
|
||||
|
||||
## Rollup
|
||||
[Rollup](https://rollupjs.org/) is a another popular build tool which recently had it's 1.0 release. It's especially suitable for optimizing and minifying es modules, and it can output es modules for browsers which have native support. We don't have a recommended setup for rollup yet.
|
||||
[Rollup](https://rollupjs.org/) is a another popular build tool which recently had it's 1.0 release. It's especially suitable for optimizing and minifying es modules, and it can output es modules for browsers which have native support. See our [default configuration](/building/building-rollup.html) to get started.
|
||||
|
||||
## Polyfills loader
|
||||
Web components are not supported yet on Edge and Internet Explorer 11. The recommended approach is to conditionally load the polyfills at runtime using our [Polyfills Loader](/building/polyfills-loader.html) for more.
|
||||
|
||||
1
docs/building/building-rollup.md
Symbolic link
1
docs/building/building-rollup.md
Symbolic link
@@ -0,0 +1 @@
|
||||
../../packages/building-rollup/README.md
|
||||
196
packages/building-rollup/README.md
Normal file
196
packages/building-rollup/README.md
Normal file
@@ -0,0 +1,196 @@
|
||||
# Rollup
|
||||
|
||||
[//]: # (AUTO INSERT HEADER PREPUBLISH)
|
||||
|
||||
## Default configuration
|
||||
We provide a good default configuration to help you get started using rollup with web components and modules.
|
||||
|
||||
Our configuration contains the minimal requirements for getting your app up and running, providing the necessary polyfills for older browsers. For more customization, such as installing custom babel plugins, see the extending section below.
|
||||
|
||||
## Manual setup
|
||||
|
||||
1. Install the required dependencies:
|
||||
```bash
|
||||
npm i -D @open-wc/building-rollup rollup rimraf http-server
|
||||
```
|
||||
|
||||
2. Create a file `rollup.config.js` and pass in your app's index.html:
|
||||
```javascript
|
||||
import createDefaultConfig from '@open-wc/building-rollup/modern-config';
|
||||
|
||||
export default createDefaultConfig({ input: './index.html' });
|
||||
```
|
||||
|
||||
Our rollup config will look through your index.html and extract all module scripts and feed them to rollup.
|
||||
|
||||
3. Create an `index.html`:
|
||||
```html
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<your-app></your-app>
|
||||
|
||||
<script type="module" src="./your-app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Note: our config will **not** handle inline module such as:
|
||||
```html
|
||||
<script type="module">
|
||||
import { MyApp } from './my-app';
|
||||
</script>
|
||||
```
|
||||
|
||||
4. Add the following commands to your `package.json`:
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"build": "rimraf dist && rollup",
|
||||
"start:build": "http-server dist -o",
|
||||
"watch:build": "rimraf dist && rollup --watch & http-server dist -o",
|
||||
}
|
||||
}
|
||||
```
|
||||
- `build` builds your app and outputs it in your `dist` directory
|
||||
- `start:build` runs your built app from `dist` directory
|
||||
- `watch:build` builds and runs your app, rebuilding when input files change
|
||||
|
||||
## Supporting legacy browsers
|
||||
`modern-config.js` we setup above works for modern browsers (see config features for more details).
|
||||
|
||||
|
||||
If you need to support older browsers, use our `modern-and-legacy-config.js` in your `rollup.config.js`:
|
||||
|
||||
```javascript
|
||||
import createDefaultConfig from '@open-wc/building-rollup/modern-and-legacy-config';
|
||||
|
||||
export default createDefaultConfig({ input: './index.html' });
|
||||
```
|
||||
|
||||
In addition to outputting your app as a module, it outputs a legacy build of your app and loads the appropriate version based on browser support. Depending on your app's own code, this will work on Chrome, Safari, Firefox, Edge and IE11.
|
||||
|
||||
## Config features
|
||||
`modern-config.js`:
|
||||
- compatible with (Chrome 63+, Safari 11.1+, Firefox 67+)
|
||||
- babel transform based on browser support (no es5)
|
||||
- output es modules using native dynamic import
|
||||
- resolve bare imports ( `import { html } from 'lit-html'` )
|
||||
- preserve `import.meta.url` value from before bundling
|
||||
- minify + treeshake js
|
||||
- minify html and css in template literals
|
||||
|
||||
`modern-and-legacy-config.js`:
|
||||
|
||||
**Modern build:**
|
||||
- compatible with latest 2 versions of chrome, safari, firefox and edge
|
||||
- babel transform based on browser support (no es5)
|
||||
- es modules
|
||||
- dynamic import polyfill
|
||||
|
||||
**Legacy build**
|
||||
- compatible down to IE11
|
||||
- babel transform down to IE11 (es5)
|
||||
- core js babel polyfills (`Array.from`, `String.prototype.includes` etc.)
|
||||
- systemjs modules
|
||||
|
||||
**Both**
|
||||
- resolve bare imports ( `import { html } from 'lit-html'` )
|
||||
- web component polyfills
|
||||
- preserve `import.meta.url` value from before bundling
|
||||
- minify + treeshake js
|
||||
- minify html and css in template literals
|
||||
|
||||
## Config options
|
||||
Our config accepts two options. Any further configuration can be done by extending the config
|
||||
```javascript
|
||||
export default createDefaultConfig({
|
||||
// your app's index.html. required
|
||||
input: './index.html',
|
||||
// the directory to output files into, defaults to 'dist'. optional
|
||||
outputDir: '',
|
||||
});
|
||||
```
|
||||
|
||||
## Customizing the babel config
|
||||
You can define your own babel plugins by adding a `.babelrc` or `babel.config.js` to your project. See [babeljs config](https://babeljs.io/docs/en/configuration) for more information.
|
||||
|
||||
For example to add support for class properties:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Extending the rollup config
|
||||
The rollup config is just a plain object. It's easy to extend it using javascript:
|
||||
```javascript
|
||||
import createDefaultConfig from '@open-wc/building-rollup/modern-config';
|
||||
|
||||
const config = createDefaultConfig({ input: './index.html' });
|
||||
|
||||
export default {
|
||||
output: {
|
||||
...config.output,
|
||||
sourcemap: false,
|
||||
},
|
||||
plugins: {
|
||||
...config.plugins,
|
||||
myAwesomePlugin(),
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Common extensions
|
||||
::: warning
|
||||
Many extensions add non-native syntax to your code, which can be bad for maintenance longer term.
|
||||
We suggest sticking to native syntax.
|
||||
If you really need it scroll below to see some usage examples.
|
||||
:::
|
||||
|
||||
#### Resolve commonjs
|
||||
CommonJS is the module format for NodeJS, and not suitable for the browser. Rollup only handles es modules by default, but sometimes it's necessray to be able to import a dependency. To do this, you can add [rollup-plugin-commonjs](https://github.com/rollup/rollup-plugin-commonjs):
|
||||
```javascript
|
||||
import commonjs from 'rollup-plugin-commonjs';
|
||||
|
||||
const config = createDefaultConfig({ input: './index.html' });
|
||||
|
||||
export default {
|
||||
plugins: [
|
||||
...config.plugins,
|
||||
commonjs()
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
#### Import CSS files in lit-html
|
||||
To separate your lit-html styles in css files, you can use [rollup-plugin-lit-css](https://github.com/bennypowers/rollup-plugin-lit-css):
|
||||
|
||||
```javascript
|
||||
import litcss from 'rollup-plugin-lit-css';
|
||||
|
||||
const config = createDefaultConfig({ input: './index.html' });
|
||||
|
||||
export default {
|
||||
plugins: [
|
||||
...config.plugins,
|
||||
litcss({ include, exclude, uglify })
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
<script>
|
||||
export default {
|
||||
mounted() {
|
||||
const editLink = document.querySelector('.edit-link a');
|
||||
if (editLink) {
|
||||
const url = editLink.href;
|
||||
editLink.href = url.substr(0, url.indexOf('/master/')) + '/master/packages/building-rollup/README.md';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
5
packages/building-rollup/demo/.babelrc
Normal file
5
packages/building-rollup/demo/.babelrc
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
]
|
||||
}
|
||||
1
packages/building-rollup/demo/a/b/foo.txt
Normal file
1
packages/building-rollup/demo/a/b/foo.txt
Normal file
@@ -0,0 +1 @@
|
||||
hello world
|
||||
14
packages/building-rollup/demo/a/b/meta-url-test-3.js
Normal file
14
packages/building-rollup/demo/a/b/meta-url-test-3.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/* eslint-disable */
|
||||
import request from '../../request';
|
||||
|
||||
console.log(
|
||||
'meta-url-test-3.js import.meta.url correct: ' +
|
||||
import.meta.url.endsWith('/demo/lazy/import-meta/meta-url-test-3.js'),
|
||||
import.meta.url,
|
||||
);
|
||||
|
||||
const basePath = new URL('./', import.meta.url);
|
||||
|
||||
request(`${basePath}foo.txt`)
|
||||
.then(txt => console.log(`foo.txt evaluated to: ${txt}`))
|
||||
.catch(console.error);
|
||||
15
packages/building-rollup/demo/a/meta-url-test-2.js
Normal file
15
packages/building-rollup/demo/a/meta-url-test-2.js
Normal file
@@ -0,0 +1,15 @@
|
||||
/* eslint-disable */
|
||||
import request from '../request';
|
||||
import './b/meta-url-test-3';
|
||||
|
||||
console.log(
|
||||
'meta-url-test-2.js import.meta.url correct: ' +
|
||||
import.meta.url.endsWith('/demo/lazy/import-meta/meta-url-test-2.js'),
|
||||
import.meta.url,
|
||||
);
|
||||
|
||||
const basePath = new URL('./', import.meta.url);
|
||||
|
||||
request(`${basePath}b/foo.txt`)
|
||||
.then(txt => console.log(`foo.txt evaluated to: ${txt}`))
|
||||
.catch(console.error);
|
||||
85
packages/building-rollup/demo/demo-app.js
Normal file
85
packages/building-rollup/demo/demo-app.js
Normal file
@@ -0,0 +1,85 @@
|
||||
/* eslint-disable */
|
||||
import { LitElement, html, css } from 'lit-element';
|
||||
import './demo-component.js';
|
||||
import './meta-url-test.js';
|
||||
|
||||
console.log('file name should end with demo/demo-app.js', import.meta.url);
|
||||
|
||||
// some simple tests to see if compilation worked
|
||||
if (!'foo'.startsWith('foo')) {
|
||||
throw new Error('startsWith failed');
|
||||
}
|
||||
|
||||
if (!'foo'.endsWith('foo')) {
|
||||
throw new Error('startsWith failed');
|
||||
}
|
||||
|
||||
if (new Map().set('foo', 'bar').get('foo') !== 'bar') {
|
||||
throw new Error('map failed');
|
||||
}
|
||||
|
||||
async function asyncFunction() {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
asyncFunction();
|
||||
console.log('async function compiled to: ', asyncFunction.toString());
|
||||
|
||||
function forOf() {
|
||||
const map = new Map();
|
||||
map.set('a', 1);
|
||||
map.set('2', 2);
|
||||
for (const [k, v] of map) {
|
||||
console.log(k, v);
|
||||
}
|
||||
}
|
||||
forOf();
|
||||
console.log('forOf function compiled to: ', forOf.toString());
|
||||
|
||||
class DemoApp extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
_myElementLoaded: { type: Boolean },
|
||||
};
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
color: black;
|
||||
background-color: white;
|
||||
}
|
||||
`;
|
||||
|
||||
foo = '123';
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._myElementLoaded = false;
|
||||
this.addEventListener('foo-event', e => {
|
||||
console.log('foo event fired through multiple shadow roots', e.composedPath());
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<p>Hello world ${this.foo}</p>
|
||||
|
||||
<button @click="${this._lazyLoad}">Lazy load</button>
|
||||
<demo-component></demo-component>
|
||||
|
||||
${this._myElementLoaded
|
||||
? html`
|
||||
<lazy-component></lazy-component>
|
||||
`
|
||||
: ''}
|
||||
`;
|
||||
}
|
||||
|
||||
async _lazyLoad() {
|
||||
await import('./lazy/lazy-component.js');
|
||||
this._myElementLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-app', DemoApp);
|
||||
26
packages/building-rollup/demo/demo-component.js
Normal file
26
packages/building-rollup/demo/demo-component.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/* eslint-disable class-methods-use-this, import/no-extraneous-dependencies */
|
||||
import { LitElement, html, css } from 'lit-element';
|
||||
|
||||
class DemoComponent extends LitElement {
|
||||
static get styles() {
|
||||
return css`
|
||||
p {
|
||||
color: blue;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<p>Demo component</p>
|
||||
`;
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.shadowRoot
|
||||
.querySelector('p')
|
||||
.dispatchEvent(new CustomEvent('foo-event', { bubbles: true, composed: true }));
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-component', DemoComponent);
|
||||
20
packages/building-rollup/demo/index.html
Normal file
20
packages/building-rollup/demo/index.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
<meta http-equiv="expires" content="0">
|
||||
|
||||
<title>My Demo</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Static content in index.html is preserved</h1>
|
||||
|
||||
<demo-app></demo-app>
|
||||
<script type="module" src="./demo-app.js"></script>
|
||||
<script type="module" src="./a/meta-url-test-2.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,14 @@
|
||||
/* eslint-disable */
|
||||
import request from '../../request';
|
||||
|
||||
console.log(
|
||||
'meta-url-test-4.js import.meta.url correct: ' +
|
||||
import.meta.url.endsWith('/demo/lazy/import-meta/meta-url-test-4.js'),
|
||||
import.meta.url,
|
||||
);
|
||||
|
||||
const basePath = new URL('./', import.meta.url);
|
||||
|
||||
request(`${basePath}../../a/b/foo.txt`)
|
||||
.then(txt => console.log(`foo.txt evaluated to: ${txt}`))
|
||||
.catch(console.error);
|
||||
28
packages/building-rollup/demo/lazy/lazy-component.js
Normal file
28
packages/building-rollup/demo/lazy/lazy-component.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/* eslint-disable */
|
||||
import { LitElement, html, css } from 'lit-element';
|
||||
|
||||
import('./import-meta/meta-url-test-4.js');
|
||||
|
||||
console.log(
|
||||
'lazy-component.js import.meta.url correct: ' +
|
||||
import.meta.url.endsWith('/demo/lazy/lazy-component.js'),
|
||||
import.meta.url,
|
||||
);
|
||||
|
||||
class LazyComponent extends LitElement {
|
||||
static get styles() {
|
||||
return css`
|
||||
p {
|
||||
color: red;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<p>Hello from lazy loaded component</p>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('lazy-component', LazyComponent);
|
||||
13
packages/building-rollup/demo/meta-url-test.js
Normal file
13
packages/building-rollup/demo/meta-url-test.js
Normal file
@@ -0,0 +1,13 @@
|
||||
/* eslint-disable */
|
||||
import request from './request';
|
||||
|
||||
console.log(
|
||||
'meta-url-test.js import.meta.url correct: ' + import.meta.url.endsWith('/demo/meta-url-test.js'),
|
||||
import.meta.url,
|
||||
);
|
||||
|
||||
const basePath = new URL('./', import.meta.url);
|
||||
|
||||
request(`${basePath}a/b/foo.txt`)
|
||||
.then(txt => console.log(`foo.txt evaluated to: ${txt}`))
|
||||
.catch(console.error);
|
||||
15
packages/building-rollup/demo/request.js
Normal file
15
packages/building-rollup/demo/request.js
Normal file
@@ -0,0 +1,15 @@
|
||||
export default url =>
|
||||
new Promise((resolve, reject) => {
|
||||
const xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = () => {
|
||||
if (xhttp.readyState === 4) {
|
||||
if (xhttp.status === 200) {
|
||||
resolve(xhttp.responseText);
|
||||
} else {
|
||||
reject(new Error(`Request to ${url} failed with ${xhttp.status}`));
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open('GET', url, true);
|
||||
xhttp.send();
|
||||
});
|
||||
25
packages/building-rollup/demo/rollup.config.js
Normal file
25
packages/building-rollup/demo/rollup.config.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import copy from 'rollup-plugin-copier';
|
||||
import createDefaultConfig from '../modern-and-legacy-config';
|
||||
|
||||
const config = createDefaultConfig({
|
||||
input: './demo/index.html',
|
||||
});
|
||||
|
||||
export default [
|
||||
{
|
||||
...config[0],
|
||||
plugins: [
|
||||
...config[0].plugins,
|
||||
copy({
|
||||
items: [
|
||||
{
|
||||
src: 'demo/a/b/foo.txt',
|
||||
dest: 'dist/demo/a/b/foo.txt',
|
||||
createPath: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
},
|
||||
config[1],
|
||||
];
|
||||
22
packages/building-rollup/demo/rollup.modern.config.js
Normal file
22
packages/building-rollup/demo/rollup.modern.config.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import copy from 'rollup-plugin-copier';
|
||||
import createDefaultConfig from '../modern-config';
|
||||
|
||||
const config = createDefaultConfig({
|
||||
input: './demo/index.html',
|
||||
});
|
||||
|
||||
export default {
|
||||
...config,
|
||||
plugins: [
|
||||
...config.plugins,
|
||||
copy({
|
||||
items: [
|
||||
{
|
||||
src: 'demo/a/b/foo.txt',
|
||||
dest: 'dist/demo/a/b/foo.txt',
|
||||
createPath: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
};
|
||||
17
packages/building-rollup/demo/rollup.visualizer.config.js
Normal file
17
packages/building-rollup/demo/rollup.visualizer.config.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import visualizer from 'rollup-plugin-visualizer';
|
||||
import createDefaultConfig from '../modern-and-legacy-config';
|
||||
|
||||
const configs = createDefaultConfig({
|
||||
input: './demo/index.html',
|
||||
});
|
||||
const config = configs[0];
|
||||
|
||||
export default {
|
||||
...config,
|
||||
plugins: [
|
||||
...config.plugins,
|
||||
visualizer({
|
||||
sourcemap: true,
|
||||
}),
|
||||
],
|
||||
};
|
||||
89
packages/building-rollup/modern-and-legacy-config.js
Normal file
89
packages/building-rollup/modern-and-legacy-config.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import resolve from 'rollup-plugin-node-resolve';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
import babel from 'rollup-plugin-babel';
|
||||
import minifyHTML from 'rollup-plugin-minify-html-literals';
|
||||
import modernWeb from './plugins/rollup-plugin-modern-web/rollup-plugin-modern-web.js';
|
||||
|
||||
const production = !process.env.ROLLUP_WATCH;
|
||||
const prefix = '[owc-building-rollup]';
|
||||
|
||||
function createConfig(_options, legacy) {
|
||||
const options = {
|
||||
outputDir: 'dist',
|
||||
..._options,
|
||||
};
|
||||
|
||||
return {
|
||||
input: options.input,
|
||||
treeshake: !!production,
|
||||
output: {
|
||||
// output into given folder or default /dist. Output legacy into a /legacy subfolder
|
||||
dir: `${options.outputDir}${legacy ? '/legacy' : ''}`,
|
||||
format: legacy ? 'system' : 'esm',
|
||||
sourcemap: true,
|
||||
dynamicImportFunction: !legacy && 'importModule',
|
||||
},
|
||||
plugins: [
|
||||
// minify html and css template literals if in production
|
||||
production &&
|
||||
minifyHTML({
|
||||
failOnError: true,
|
||||
}),
|
||||
|
||||
// parse input index.html as input, feed any modules found to rollup and add polyfills
|
||||
modernWeb({
|
||||
legacy,
|
||||
polyfillDynamicImports: !legacy,
|
||||
polyfillBabel: legacy,
|
||||
polyfillWebcomponents: legacy,
|
||||
}),
|
||||
|
||||
// resolve bare import specifiers
|
||||
resolve(),
|
||||
|
||||
// run code through babel
|
||||
babel({
|
||||
plugins: [
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-syntax-import-meta',
|
||||
// rollup rewrites import.meta.url, but makes them point to the file location after bundling
|
||||
// we want the location before bundling
|
||||
['bundled-import-meta', { importStyle: 'baseURI' }],
|
||||
],
|
||||
presets: [
|
||||
[
|
||||
'@babel/env',
|
||||
{
|
||||
targets: legacy
|
||||
? ['ie 11']
|
||||
: [
|
||||
'last 2 Chrome major versions',
|
||||
'last 2 ChromeAndroid major versions',
|
||||
'last 2 Edge major versions',
|
||||
'last 2 Firefox major versions',
|
||||
'last 2 Safari major versions',
|
||||
'last 2 iOS major versions',
|
||||
],
|
||||
useBuiltIns: false,
|
||||
},
|
||||
],
|
||||
],
|
||||
}),
|
||||
|
||||
// only minify if in production
|
||||
production && terser(),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export default function createDefaultConfig(options) {
|
||||
if (!options.input) {
|
||||
throw new Error(`${prefix}: missing option 'input'.`);
|
||||
}
|
||||
|
||||
if (typeof options.input !== 'string' || !options.input.endsWith('.html')) {
|
||||
throw new Error(`${prefix}: input should point to a single .html file.`);
|
||||
}
|
||||
|
||||
return [createConfig(options, false), createConfig(options, true)];
|
||||
}
|
||||
67
packages/building-rollup/modern-config.js
Normal file
67
packages/building-rollup/modern-config.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import resolve from 'rollup-plugin-node-resolve';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
import babel from 'rollup-plugin-babel';
|
||||
import minifyHTML from 'rollup-plugin-minify-html-literals';
|
||||
import modernWeb from './plugins/rollup-plugin-modern-web/rollup-plugin-modern-web.js';
|
||||
|
||||
const production = !process.env.ROLLUP_WATCH;
|
||||
|
||||
export default function createBasicConfig(_options) {
|
||||
const options = {
|
||||
outputDir: 'dist',
|
||||
..._options,
|
||||
};
|
||||
|
||||
return {
|
||||
input: options.input,
|
||||
treeshake: !!production,
|
||||
output: {
|
||||
dir: options.outputDir,
|
||||
format: 'esm',
|
||||
sourcemap: true,
|
||||
},
|
||||
plugins: [
|
||||
// minify html and css template literals if in production
|
||||
production &&
|
||||
minifyHTML({
|
||||
failOnError: true,
|
||||
}),
|
||||
|
||||
// parse input index.html as input and feed any modules found to rollup
|
||||
modernWeb(),
|
||||
|
||||
// resolve bare import specifiers
|
||||
resolve(),
|
||||
|
||||
// run code through babel
|
||||
babel({
|
||||
plugins: [
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-syntax-import-meta',
|
||||
// rollup rewrites import.meta.url, but makes them point to the file location after bundling
|
||||
// we want the location before bundling
|
||||
'bundled-import-meta',
|
||||
],
|
||||
presets: [
|
||||
[
|
||||
'@babel/env',
|
||||
{
|
||||
targets: [
|
||||
'last 2 Chrome major versions',
|
||||
'last 2 ChromeAndroid major versions',
|
||||
'last 2 Edge major versions',
|
||||
'last 2 Firefox major versions',
|
||||
'last 2 Safari major versions',
|
||||
'last 2 iOS major versions',
|
||||
],
|
||||
useBuiltIns: false,
|
||||
},
|
||||
],
|
||||
],
|
||||
}),
|
||||
|
||||
// only minify if in production
|
||||
production && terser(),
|
||||
],
|
||||
};
|
||||
}
|
||||
62
packages/building-rollup/package.json
Normal file
62
packages/building-rollup/package.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "@open-wc/building-rollup",
|
||||
"version": "0.0.0",
|
||||
"description": "Default configuration for working with rollup",
|
||||
"author": "open-wc",
|
||||
"homepage": "https://github.com/open-wc/open-wc/",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/open-wc/open-wc.git",
|
||||
"directory": "packages/building-rollup"
|
||||
},
|
||||
"scripts": {
|
||||
"build:demo": "rimraf dist && rollup -c demo/rollup.config.js",
|
||||
"start:demo": "http-server dist -o",
|
||||
"watch:demo": "rimraf dist && rollup -c demo/rollup.config.js --watch & http-server dist -o",
|
||||
"visualizer:demo": "rimraf dist && rollup -c demo/rollup.visualizer.config.js",
|
||||
"build-modern:demo": "rimraf dist && rollup -c demo/rollup.modern.config.js",
|
||||
"start-modern:demo": "http-server dist -o",
|
||||
"watch-modern:demo": "rimraf dist && rollup -c demo/rollup.modern.config.js --watch & http-server dist -o",
|
||||
"prepublishOnly": "../../scripts/insert-header.js"
|
||||
},
|
||||
"keywords": [
|
||||
"open-wc",
|
||||
"web-components",
|
||||
"modules",
|
||||
"rollup"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.3.3",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||
"@babel/plugin-syntax-import-meta": "^7.2.0",
|
||||
"@babel/polyfill": "^7.2.5",
|
||||
"@babel/preset-env": "^7.3.1",
|
||||
"@webcomponents/webcomponentsjs": "^2.2.7",
|
||||
"babel-plugin-bundled-import-meta": "^0.3.0",
|
||||
"dom5": "^3.0.1",
|
||||
"mkdirp": "^0.5.1",
|
||||
"parse5": "^5.1.0",
|
||||
"rollup-plugin-babel": "^4.3.2",
|
||||
"rollup-plugin-minify-html-literals": "^1.2.2",
|
||||
"rollup-plugin-node-resolve": "^4.0.1",
|
||||
"rollup-plugin-polyfill": "^2.0.1",
|
||||
"rollup-plugin-terser": "^4.0.4",
|
||||
"systemjs": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.3.4",
|
||||
"@babel/plugin-proposal-decorators": "^7.3.0",
|
||||
"http-server": "^0.11.1",
|
||||
"lit-element": "^2.0.1",
|
||||
"owc-dev-server": "^0.1.6",
|
||||
"rimraf": "^2.6.3",
|
||||
"rollup": "^1.4.0",
|
||||
"rollup-plugin-copier": "^1.1.0",
|
||||
"rollup-plugin-copy-assets": "^1.1.0",
|
||||
"rollup-plugin-visualizer": "^1.0.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
import path from 'path';
|
||||
import mkdirp from 'mkdirp';
|
||||
import { copyFileSync, existsSync } from 'fs';
|
||||
import { query, queryAll, predicates, getAttribute, append, remove } from 'dom5';
|
||||
import {
|
||||
readHTML,
|
||||
writeOutputHTML,
|
||||
createElement,
|
||||
createScript,
|
||||
createScriptModule,
|
||||
} from './utils.js';
|
||||
|
||||
const prefix = '[rollup-plugin-legacy-browsers]:';
|
||||
let writtenModules = false;
|
||||
let writtenLegacyModules = false;
|
||||
let inputIndexHTMLPath;
|
||||
|
||||
// Gets the output HTML as AST. Rollup might run with multiple outputs, but there
|
||||
// should be only one index.html output. Therefore we check if it wasn't already
|
||||
// created before. If there is no index.html yet, we copy over the input index.html
|
||||
// with the original module scripts removed.
|
||||
function getOutputHTML(dir) {
|
||||
if (existsSync(`${dir}/index.html`)) {
|
||||
return readHTML(`${dir}/index.html`);
|
||||
}
|
||||
|
||||
const outputHTML = readHTML(inputIndexHTMLPath);
|
||||
const scripts = queryAll(outputHTML, predicates.hasTagName('script'));
|
||||
const moduleScripts = scripts.filter(script => getAttribute(script, 'type') === 'module');
|
||||
moduleScripts.forEach(moduleScript => {
|
||||
remove(moduleScript);
|
||||
});
|
||||
|
||||
writeOutputHTML(dir, outputHTML);
|
||||
return outputHTML;
|
||||
}
|
||||
|
||||
// Copy required polyfills from node_modules to the build directory
|
||||
function copyPolyfills(pluginConfig, outputConfig) {
|
||||
if (
|
||||
!pluginConfig.polyfillDynamicImports &&
|
||||
!pluginConfig.polyfillWebcomponents &&
|
||||
!pluginConfig.polyfillBabel
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const polyfillsDir = `${outputConfig.dir.replace('/legacy', '')}/polyfills`;
|
||||
mkdirp.sync(polyfillsDir);
|
||||
|
||||
if (pluginConfig.polyfillDynamicImports) {
|
||||
copyFileSync(
|
||||
path.resolve('./src/dynamic-import-polyfill.js'),
|
||||
`${polyfillsDir}/dynamic-import-polyfill.js`,
|
||||
);
|
||||
}
|
||||
|
||||
if (pluginConfig.polyfillWebcomponents) {
|
||||
copyFileSync(
|
||||
require.resolve('@webcomponents/webcomponentsjs/webcomponents-bundle.js'),
|
||||
`${polyfillsDir}/webcomponent-polyfills.js`,
|
||||
);
|
||||
}
|
||||
|
||||
if (pluginConfig.polyfillBabel) {
|
||||
copyFileSync(
|
||||
require.resolve('@babel/polyfill/browser.js'),
|
||||
`${polyfillsDir}/babel-polyfills.js`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Writes module scripts to output HTML file and copy necessary polyfills.
|
||||
function writeModules(pluginConfig, outputConfig, entryModules) {
|
||||
const indexHTML = getOutputHTML(outputConfig.dir);
|
||||
const head = query(indexHTML, predicates.hasTagName('head'));
|
||||
const body = query(indexHTML, predicates.hasTagName('body'));
|
||||
|
||||
// If dynamic imports are polyfilled, we first need to feature detect so we can't load our app using the
|
||||
// 'src' attribute.
|
||||
if (pluginConfig.polyfillDynamicImports) {
|
||||
// Feature detect dynamic import in a separate script, because browsers which don't
|
||||
// support it will throw a syntax error and stop executing the module
|
||||
append(body, createScriptModule('window.importModule=src=>import(src);'));
|
||||
|
||||
// The actual loading script. Loads web components and dynamic import polyfills if necessary, then
|
||||
// loads the user's modules
|
||||
append(
|
||||
body,
|
||||
createScriptModule(`
|
||||
(async () => {
|
||||
const p = [];
|
||||
if (!('attachShadow' in Element.prototype) || !('getRootNode' in Element.prototype)) {p.push('./polyfills/webcomponent-polyfills.js');}
|
||||
if (!window.importModule) {p.push('./polyfills/dynamic-import-polyfill.js');}
|
||||
if (p.length > 0) {await Promise.all(p.map(p => new Promise((rs, rj) => {const s = document.createElement('script');s.src = p;s.onerror = () => rj(\`Failed to load \${s.s}\`);s.onload = () => rs();document.head.appendChild(s);})));}
|
||||
${entryModules.map(src => `importModule('${src}');`).join('')}
|
||||
})();
|
||||
`),
|
||||
);
|
||||
|
||||
// Because the module is loaded after some js execution, add preload hints so that it is downloaded
|
||||
// from the start
|
||||
entryModules.forEach(src => {
|
||||
append(
|
||||
head,
|
||||
createElement('link', {
|
||||
rel: 'preload',
|
||||
as: 'script',
|
||||
crossorigin: 'anonymous',
|
||||
href: src,
|
||||
}),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// No polyfilling needed, so just create a module script
|
||||
entryModules.forEach(src => {
|
||||
append(body, createScript({ type: 'module', src }));
|
||||
});
|
||||
}
|
||||
|
||||
writeOutputHTML(outputConfig.dir, indexHTML);
|
||||
}
|
||||
|
||||
// Browsers which don't support es modules also don't support es2015 and web components
|
||||
// for those browsers we load a systemjs build, including:
|
||||
// - es5 code
|
||||
// - babel polyfills
|
||||
// - web component polyfills (no feature detection needed)
|
||||
// - systemjs loader for modules
|
||||
function writeLegacyModules(pluginConfig, outputConfig, entryModules) {
|
||||
const legacyOutputDir = outputConfig.dir;
|
||||
const outputDir = legacyOutputDir.replace('/legacy', '');
|
||||
const indexHTML = getOutputHTML(outputDir);
|
||||
|
||||
const body = query(indexHTML, predicates.hasTagName('body'));
|
||||
const polyfillsDir = `${outputDir}/polyfills`;
|
||||
mkdirp.sync(polyfillsDir);
|
||||
|
||||
if (pluginConfig.polyfillBabel) {
|
||||
append(
|
||||
body,
|
||||
createScript({
|
||||
src: `./polyfills/babel-polyfills.js`,
|
||||
nomodule: '',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (pluginConfig.polyfillWebcomponents) {
|
||||
append(
|
||||
body,
|
||||
createScript({
|
||||
src: `./polyfills/webcomponent-polyfills.js`,
|
||||
nomodule: '',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
copyFileSync(require.resolve('systemjs/dist/s.min.js'), `${polyfillsDir}/system-loader.js`);
|
||||
append(
|
||||
body,
|
||||
createScript({
|
||||
src: `./polyfills/system-loader.js`,
|
||||
nomodule: '',
|
||||
}),
|
||||
);
|
||||
|
||||
const loadScript = createScript(
|
||||
{ nomodule: '' },
|
||||
entryModules.map(src => `System.import('./${path.join('legacy', src)}');`).join(''),
|
||||
);
|
||||
append(body, loadScript);
|
||||
writeOutputHTML(outputDir, indexHTML);
|
||||
}
|
||||
|
||||
export default (_pluginConfig = {}) => {
|
||||
const pluginConfig = {
|
||||
// Whether this the systemjs output for legacy browsers
|
||||
legacy: false,
|
||||
// Whether to inject a polyfill for dynamic imports if there is no native support.
|
||||
polyfillDynamicImports: false,
|
||||
// Whether to inject babel polyfills (Object.assign, Promise, Array.prototype.some etc.)
|
||||
polyfillBabel: false,
|
||||
// Whether to inject webcomponentsjs polyfills. These are only added if on the legacy build
|
||||
polyfillWebcomponents: false,
|
||||
..._pluginConfig,
|
||||
};
|
||||
|
||||
return {
|
||||
name: 'modern-web',
|
||||
|
||||
// Takes the configured index.html input, looks for all defined module scripts and feeds them to rollup.
|
||||
options(config) {
|
||||
if (typeof config.input === 'string' && config.input.endsWith('index.html')) {
|
||||
inputIndexHTMLPath = config.input;
|
||||
const indexHTML = readHTML(config.input);
|
||||
const scripts = queryAll(indexHTML, predicates.hasTagName('script'));
|
||||
const moduleScripts = scripts.filter(script => getAttribute(script, 'type') === 'module');
|
||||
|
||||
if (moduleScripts.length === 0) {
|
||||
throw new Error(
|
||||
`${prefix} Could not find any module script in configured input: ${config.input}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (moduleScripts.some(script => !getAttribute(script, 'src'))) {
|
||||
throw new Error(`${prefix} Module scripts without a 'src' attribute are not supported.`);
|
||||
}
|
||||
const indexDir = path.dirname(config.input);
|
||||
|
||||
const modules = moduleScripts.map(script => getAttribute(script, 'src'));
|
||||
return {
|
||||
...config,
|
||||
input: modules.map(p => path.join(indexDir, p)),
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
// Injects generated module paths into index.html
|
||||
generateBundle(outputConfig, bundles) {
|
||||
const entryModules = Object.keys(bundles)
|
||||
.filter(key => bundles[key].isEntry)
|
||||
.map(e => `./${e}`);
|
||||
|
||||
if (!pluginConfig.legacy && !writtenModules) {
|
||||
copyPolyfills(pluginConfig, outputConfig);
|
||||
writeModules(pluginConfig, outputConfig, entryModules);
|
||||
writtenModules = true;
|
||||
} else if (!writtenLegacyModules) {
|
||||
copyPolyfills(pluginConfig, outputConfig);
|
||||
writeLegacyModules(pluginConfig, outputConfig, entryModules);
|
||||
writtenLegacyModules = true;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import path from 'path';
|
||||
import mkdirp from 'mkdirp';
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
import { parse, serialize } from 'parse5';
|
||||
import { constructors, setAttribute, append } from 'dom5';
|
||||
|
||||
/** Reads file as HTML AST */
|
||||
export function readHTML(file) {
|
||||
return parse(readFileSync(file, 'utf-8'));
|
||||
}
|
||||
|
||||
/** Writes given HTML AST to output index.html */
|
||||
export function writeOutputHTML(dir, html) {
|
||||
const outputPath = path.join(dir, 'index.html');
|
||||
mkdirp.sync(path.dirname(outputPath));
|
||||
writeFileSync(outputPath, serialize(html));
|
||||
}
|
||||
|
||||
export function createElement(tag, attributes) {
|
||||
const element = constructors.element(tag);
|
||||
if (attributes) {
|
||||
Object.keys(attributes).forEach(key => {
|
||||
setAttribute(element, key, attributes[key]);
|
||||
});
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
export function createScript(attributes, code) {
|
||||
const script = createElement('script', attributes);
|
||||
if (code) {
|
||||
const scriptText = constructors.text(code);
|
||||
append(script, scriptText);
|
||||
}
|
||||
return script;
|
||||
}
|
||||
|
||||
export function createScriptModule(code) {
|
||||
return createScript({ type: 'module' }, code);
|
||||
}
|
||||
38
packages/building-rollup/src/dynamic-import-polyfill.js
Normal file
38
packages/building-rollup/src/dynamic-import-polyfill.js
Normal file
@@ -0,0 +1,38 @@
|
||||
function toAbsoluteURL(url) {
|
||||
const a = document.createElement('a');
|
||||
a.setAttribute('href', url); // <a href="hoge.html">
|
||||
return a.cloneNode(false).href; // -> "http://example.com/hoge.html"
|
||||
}
|
||||
|
||||
window.importModule = function importModule(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const vector = `$importModule$${Math.random()
|
||||
.toString(32)
|
||||
.slice(2)}`;
|
||||
const script = document.createElement('script');
|
||||
const destructor = () => {
|
||||
delete window[vector];
|
||||
script.onerror = null;
|
||||
script.onload = null;
|
||||
script.remove();
|
||||
URL.revokeObjectURL(script.src);
|
||||
script.src = '';
|
||||
};
|
||||
script.defer = 'defer';
|
||||
script.type = 'module';
|
||||
script.onerror = () => {
|
||||
reject(new Error(`Failed to import: ${url}`));
|
||||
destructor();
|
||||
};
|
||||
script.onload = () => {
|
||||
resolve(window[vector]);
|
||||
destructor();
|
||||
};
|
||||
const absURL = toAbsoluteURL(url);
|
||||
const loader = `import * as m from "${absURL}"; window.${vector} = m;`; // export Module
|
||||
const blob = new Blob([loader], { type: 'text/javascript' });
|
||||
script.src = URL.createObjectURL(blob);
|
||||
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
};
|
||||
0
packages/owc-dev-server/owc-dev-server.js
Normal file → Executable file
0
packages/owc-dev-server/owc-dev-server.js
Normal file → Executable file
Reference in New Issue
Block a user