Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rollup-plugin-import-meta-assets): init plugin #553

Merged
merged 2 commits into from
Sep 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
/packages/test-runner-mocha/*.d.ts
/packages/config-loader/
/packages/*/test/**/fixtures
/packages/*/test/**/snapshots
/packages/dev-server-rollup/test/browser/**/*
node_modules
dist
demo
CHANGELOG.md
.changeset
_site
_site
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.js eol=lf
5 changes: 5 additions & 0 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ jobs:
name: Verify windows
runs-on: windows-latest
steps:
- name: Set git to use LF
run: |
git config --global core.autocrlf false
git config --global core.eol lf

- uses: actions/checkout@v2

- name: Setup Node 12.x
Expand Down
135 changes: 135 additions & 0 deletions docs/docs/building/rollup-plugin-import-meta-assets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
---
title: Rollup Plugin import-meta-assets
eleventyNavigation:
key: Rollup Plugin import-meta-assets
parent: Building
order: 2
---

Rollup plugin that detects assets references relative to modules using patterns such as `new URL('./path/to/asset.ext', import.meta.url)`. The assets are added to the rollup pipeline, allowing them to be transformed and hash the filenames.

## Install

Using npm:

```
npm install @web/rollup-plugin-import-meta-assets --save-dev
```

## Usage

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

```js
import { importMetaAssets } from '@web/rollup-plugin-import-meta-assets';

export default {
input: 'src/index.js',
output: {
dir: 'output',
format: 'es',
},
plugins: [importMetaAssets()],
};
```

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).

## Options

### `exclude`

Type: `String` | `Array[...String]`<br>
Default: `null`

A [picomatch pattern](https://github.com/micromatch/picomatch#globbing-features), or array of patterns, which specifies the files in the build the plugin should _ignore_.
By default no files are ignored.

### `include`

Type: `String` | `Array[...String]`<br>
Default: `null`

A [picomatch pattern](https://github.com/micromatch/picomatch#globbing-features), or array of patterns, which specifies the files in the build the plugin should operate on.
By default all files are targeted.

### `warnOnError`

Type: `Boolean`<br>
Default: `false`

By default, the plugin quits the build process when it encounters an error. If you set this option to true, it will throw a warning instead and leave the code untouched.

### `transform`

Type: `Function`<br>
Default: `null`

By default, referenced assets detected by this plugin are just copied as is to the output directory, according to your configuration.

When `transform` is defined, this function will be called for each asset with two parameters, the content of the asset as a [Buffer](https://nodejs.org/api/buffer.html) and the absolute path.
This allows you to conditionnaly match on the absolute path and maybe transform the content.

In this example, we use it to optimize SVG images with [svgo](https://github.com/svg/svgo):

```js
import { importMetaAssets } from '@web/rollup-plugin-import-meta-assets';
const svgo = new SVGO({
// See https://github.com/svg/svgo#what-it-can-do
plugins: [ /* plugins here */],
});

export default {
input: 'src/index.js',
output: {
dir: 'output',
format: 'es',
},
plugins: [
importMetaAssets({
transform: (assetBuffer, assetPath) => {
return assetPath.endsWith('.svg')
? svgo.optimize(assetBuffer.toString()).then(({ data }) => data);
: assetBuffer;
},
}),
],
};
```

## Examples

Source directory:

```
.
├── one
│ └── two
│ └── the-image.svg
├──
└── entrypoint.js
```

With `entrypoint.js` containing this:

```js
const imageUrl = new URL('./one/two/the-image.svg', import.meta.url).href;
console.log(imageUrl);
```

Output directory:

```
.
├── assets
│ └── the-image.svg
└── bundle.js
```

With `bundle.js` containing this:

```js
const imageUrl = new URL(new URL('asset/the-image.svg', import.meta.url).href, import.meta.url)
.href;
console.log(imageUrl);
```
Empty file.
34 changes: 34 additions & 0 deletions packages/rollup-plugin-import-meta-assets/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Rollup Plugin import-meta-assets

Rollup plugin that detects assets references relative to modules using patterns such as `new URL('./path/to/asset.ext', import.meta.url)`. The assets are added to the rollup pipeline, allowing them to be transformed and hash the filenames.

## Install

Using npm:

```
npm install @web/rollup-plugin-import-meta-assets --save-dev
```

## Usage

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

```js
import { importMetaAssets } from '@web/rollup-plugin-import-meta-assets';

export default {
input: 'src/index.js',
output: {
dir: 'output',
format: 'es',
},
plugins: [importMetaAssets()],
};
```

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).

## Documentation

See [our website](https://modern-web.dev/docs/building/rollup-plugin-import-meta-assets/) for full documentation.
5 changes: 5 additions & 0 deletions packages/rollup-plugin-import-meta-assets/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import cjsEntrypoint from './src/rollup-plugin-import-meta-assets.js';

const { importMetaAssets } = cjsEntrypoint;

export { importMetaAssets };
39 changes: 39 additions & 0 deletions packages/rollup-plugin-import-meta-assets/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "@web/rollup-plugin-import-meta-assets",
"version": "0.0.0",
"publishConfig": {
"access": "public"
},
"description": "Rollup plugin that detects assets references relative to modules using patterns such as `new URL('./path/to/asset.ext', import.meta.url)`. The assets are added to the rollup pipeline, allowing them to be transformed and hash the filenames.",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/modernweb-dev/web.git",
"directory": "packages/rollup-plugin-import-meta-assets"
},
"author": "modern-web",
"homepage": "https://github.com/modernweb-dev/web/tree/master/packages/rollup-plugin-import-meta-assets",
"main": "dist/index.js",
"engines": {
"node": ">=10.0.0"
},
"scripts": {
"test": "npm run test:node",
"test:node": "mocha test/**/*.test.js test/*.test.js",
"test:update-snapshots": "mocha test/**/*.test.js test/*.test.js --update-snapshots",
"test:watch": "npm run test:node -- --watch"
},
"files": [
"dist"
],
"keywords": [
"rollup",
"plugin",
"import-meta"
],
"dependencies": {
"@rollup/pluginutils": "^3.1.0",
"estree-walker": "^2.0.1",
"magic-string": "^0.25.7"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
'use strict';

const fs = require('fs');
const path = require('path');
const { createFilter } = require('@rollup/pluginutils');
const { asyncWalk } = require('estree-walker');
const MagicString = require('magic-string');

/**
* Extract the relative path from an AST node representing this kind of expression `new URL('./path/to/asset.ext', import.meta.url)`.
*
* @param {import('estree').Node} node - The AST node
* @returns {string} The relative path
*/
function getRelativeAssetPath(node) {
const browserPath = node.arguments[0].value;
return browserPath.split('/').join(path.sep);
}

/**
* Checks if a AST node represents this kind of expression: `new URL('./path/to/asset.ext', import.meta.url)`.
*
* @param {import('estree').Node} node - The AST node
* @returns {boolean}
*/
function isNewUrlImportMetaUrl(node) {
return (
node.type === 'NewExpression' &&
node.callee.type === 'Identifier' &&
node.callee.name === 'URL' &&
node.arguments.length === 2 &&
node.arguments[0].type === 'Literal' &&
typeof getRelativeAssetPath(node) === 'string' &&
node.arguments[1].type === 'MemberExpression' &&
node.arguments[1].object.type === 'MetaProperty' &&
node.arguments[1].property.type === 'Identifier' &&
node.arguments[1].property.name === 'url'
);
}

/**
* Detects assets references relative to modules using patterns such as `new URL('./path/to/asset.ext', import.meta.url)`.
* The assets are added to the rollup pipeline, allowing them to be transformed and hash the filenames.
*
* @param {object} options
* @param {string|string[]} [options.include] A picomatch pattern, or array of patterns, which specifies the files in the build the plugin should operate on. By default all files are targeted.
* @param {string|string[]} [options.exclude] A picomatch pattern, or array of patterns, which specifies the files in the build the plugin should _ignore_. By default no files are ignored.
* @param {boolean} [options.warnOnError] By default, the plugin quits the build process when it encounters an error. If you set this option to true, it will throw a warning instead and leave the code untouched.
* @param {function} [options.transform] A function to transform assets.
* @return {import('rollup').Plugin} A Rollup Plugin
*/
function importMetaAssets({ include, exclude, warnOnError, transform } = {}) {
Copy link
Member

Choose a reason for hiding this comment

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

@LarsDenBakker there are no types right? why does the linting of types does not fail?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have no idea. I'm new here 😆
Is there another plugin source I can get inspiration from?

Copy link
Member

Choose a reason for hiding this comment

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

const filter = createFilter(include, exclude);

return {
name: 'rollup-plugin-import-meta-assets',

async transform(code, id) {
if (!filter(id)) {
return null;
}

const ast = this.parse(code);
const magicString = new MagicString(code);

await asyncWalk(ast, {
enter: async node => {
if (isNewUrlImportMetaUrl(node)) {
const absoluteScriptDir = path.dirname(id);
const relativeAssetPath = getRelativeAssetPath(node);
const absoluteAssetPath = path.resolve(absoluteScriptDir, relativeAssetPath);
const assetName = path.basename(absoluteAssetPath);

try {
const assetContents = await fs.promises.readFile(absoluteAssetPath);
Copy link
Member

Choose a reason for hiding this comment

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

need to check if this works on node v10

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do. Official docs says v10. Local test with latest v10 works.

const transformedAssetContents =
transform != null
? await transform(assetContents, absoluteAssetPath)
: assetContents;
const ref = this.emitFile({
type: 'asset',
name: assetName,
source: transformedAssetContents,
});
magicString.overwrite(
node.arguments[0].start,
node.arguments[0].end,
`import.meta.ROLLUP_FILE_URL_${ref}`,
);
} catch (error) {
if (warnOnError) {
this.warn(error, node.object.arguments[0].start);
} else {
this.error(error, node.object.arguments[0].start);
}
}
}
},
});

return {
code: magicString.toString(),
map: magicString.generateMap(),
};
},
};
}

module.exports = { importMetaAssets };
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { imageOne, nameOne } from './one/one.js';
import { imageTwo, nameTwo } from './one/two/two.js';
import { imageThree, nameThree } from './one/two/three/three.js';
import { imageFour, nameFour } from './one/two/three/four/four.js';

console.log({
[nameOne]: imageOne,
[nameTwo]: imageTwo,
[nameThree]: imageThree,
[nameFour]: imageFour,
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const nameOne = 'one-name';
export const imageOne = new URL('../one.svg', import.meta.url).href;
Loading