Skip to content

Commit 1a9b1e3

Browse files
authoredDec 12, 2022
Require Node.js 16 and move to ESM (#267)
1 parent 63b601b commit 1a9b1e3

File tree

7 files changed

+160
-147
lines changed

7 files changed

+160
-147
lines changed
 

‎.github/workflows/main.yml

+4-6
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,11 @@ jobs:
1010
fail-fast: false
1111
matrix:
1212
node-version:
13-
- 14
14-
- 12
15-
- 10
16-
- 8
13+
- 18
14+
- 16
1715
steps:
18-
- uses: actions/checkout@v2
19-
- uses: actions/setup-node@v1
16+
- uses: actions/checkout@v3
17+
- uses: actions/setup-node@v3
2018
with:
2119
node-version: ${{ matrix.node-version }}
2220
- run: npm install

‎index.js

+39-50
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
'use strict';
2-
const path = require('path');
3-
const through = require('through2');
4-
const vinylFile = require('vinyl-file');
5-
const revHash = require('rev-hash');
6-
const revPath = require('rev-path');
7-
const sortKeys = require('sort-keys');
8-
const modifyFilename = require('modify-filename');
9-
const Vinyl = require('vinyl');
10-
const PluginError = require('plugin-error');
1+
import {Buffer} from 'node:buffer';
2+
import path from 'node:path';
3+
import transformStream from 'easy-transform-stream';
4+
import {vinylFile} from 'vinyl-file';
5+
import revHash from 'rev-hash';
6+
import {revPath} from 'rev-path';
7+
import sortKeys from 'sort-keys';
8+
import modifyFilename from 'modify-filename';
9+
import Vinyl from 'vinyl';
10+
import PluginError from 'plugin-error';
1111

1212
function relativePath(base, filePath) {
1313
filePath = filePath.replace(/\\/g, '/');
@@ -35,17 +35,17 @@ function transformFilename(file) {
3535
file.path = modifyFilename(file.path, (filename, extension) => {
3636
const extIndex = filename.lastIndexOf('.');
3737

38-
filename = extIndex === -1 ?
39-
revPath(filename, file.revHash) :
40-
revPath(filename.slice(0, extIndex), file.revHash) + filename.slice(extIndex);
38+
filename = extIndex === -1
39+
? revPath(filename, file.revHash)
40+
: revPath(filename.slice(0, extIndex), file.revHash) + filename.slice(extIndex);
4141

4242
return filename + extension;
4343
});
4444
}
4545

4646
const getManifestFile = async options => {
4747
try {
48-
return await vinylFile.read(options.path, options);
48+
return await vinylFile(options.path, options);
4949
} catch (error) {
5050
if (error.code === 'ENOENT') {
5151
return new Vinyl(options);
@@ -59,37 +59,36 @@ const plugin = () => {
5959
const sourcemaps = [];
6060
const pathMap = {};
6161

62-
return through.obj((file, encoding, callback) => {
62+
return transformStream({objectMode: true}, file => {
6363
if (file.isNull()) {
64-
callback(null, file);
65-
return;
64+
return file;
6665
}
6766

6867
if (file.isStream()) {
69-
callback(new PluginError('gulp-rev', 'Streaming not supported'));
70-
return;
68+
throw new PluginError('gulp-rev', 'Streaming not supported');
7169
}
7270

7371
// This is a sourcemap, hold until the end
7472
if (path.extname(file.path) === '.map') {
7573
sourcemaps.push(file);
76-
callback();
7774
return;
7875
}
7976

8077
const oldPath = file.path;
8178
transformFilename(file);
8279
pathMap[oldPath] = file.revHash;
8380

84-
callback(null, file);
85-
}, function (callback) {
81+
return file;
82+
}, () => {
83+
const files = [];
84+
8685
for (const file of sourcemaps) {
8786
let reverseFilename;
8887

8988
// Attempt to parse the sourcemap's JSON to get the reverse filename
9089
try {
9190
reverseFilename = JSON.parse(file.contents.toString()).file;
92-
} catch (_) {}
91+
} catch {}
9392

9493
if (!reverseFilename) {
9594
reverseFilename = path.relative(path.dirname(file.path), path.basename(file.path, '.map'));
@@ -106,10 +105,10 @@ const plugin = () => {
106105
transformFilename(file);
107106
}
108107

109-
this.push(file);
108+
files.push(file);
110109
}
111110

112-
callback();
111+
return files;
113112
});
114113
};
115114

@@ -123,53 +122,43 @@ plugin.manifest = (path_, options) => {
123122
merge: false,
124123
transformer: JSON,
125124
...options,
126-
...path_
125+
...path_,
127126
};
128127

129128
let manifest = {};
130129

131-
return through.obj((file, encoding, callback) => {
130+
return transformStream({objectMode: true}, file => {
132131
// Ignore all non-rev'd files
133132
if (!file.path || !file.revOrigPath) {
134-
callback();
135133
return;
136134
}
137135

138136
const revisionedFile = relativePath(path.resolve(file.cwd, file.base), path.resolve(file.cwd, file.path));
139137
const originalFile = path.join(path.dirname(revisionedFile), path.basename(file.revOrigPath)).replace(/\\/g, '/');
140138

141139
manifest[originalFile] = revisionedFile;
142-
143-
callback();
144-
}, function (callback) {
140+
}, async function * () {
145141
// No need to write a manifest file if there's nothing to manifest
146142
if (Object.keys(manifest).length === 0) {
147-
callback();
148143
return;
149144
}
150145

151-
(async () => {
152-
try {
153-
const manifestFile = await getManifestFile(options);
146+
const manifestFile = await getManifestFile(options);
154147

155-
if (options.merge && !manifestFile.isNull()) {
156-
let oldManifest = {};
148+
if (options.merge && !manifestFile.isNull()) {
149+
let oldManifest = {};
157150

158-
try {
159-
oldManifest = options.transformer.parse(manifestFile.contents.toString());
160-
} catch (_) {}
151+
try {
152+
oldManifest = options.transformer.parse(manifestFile.contents.toString());
153+
} catch {}
154+
155+
manifest = Object.assign(oldManifest, manifest);
156+
}
161157

162-
manifest = Object.assign(oldManifest, manifest);
163-
}
158+
manifestFile.contents = Buffer.from(options.transformer.stringify(sortKeys(manifest), undefined, ' '));
164159

165-
manifestFile.contents = Buffer.from(options.transformer.stringify(sortKeys(manifest), undefined, ' '));
166-
this.push(manifestFile);
167-
callback();
168-
} catch (error) {
169-
callback(error);
170-
}
171-
})();
160+
yield manifestFile;
172161
});
173162
};
174163

175-
module.exports = plugin;
164+
export default plugin;

‎package.json

+15-12
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
"description": "Static asset revisioning by appending content hash to filenames: unicorn.css => unicorn-d41d8cd98f.css",
55
"license": "MIT",
66
"repository": "sindresorhus/gulp-rev",
7+
"funding": "https://github.com/sponsors/sindresorhus",
78
"author": {
89
"name": "Sindre Sorhus",
910
"email": "sindresorhus@gmail.com",
1011
"url": "sindresorhus.com"
1112
},
13+
"type": "module",
14+
"exports": "./index.js",
1215
"engines": {
13-
"node": ">=8"
16+
"node": ">=16"
1417
},
1518
"scripts": {
1619
"test": "xo && ava"
@@ -34,19 +37,19 @@
3437
"assets"
3538
],
3639
"dependencies": {
37-
"modify-filename": "^1.1.0",
38-
"plugin-error": "^1.0.1",
39-
"rev-hash": "^3.0.0",
40-
"rev-path": "^2.0.0",
41-
"sort-keys": "^4.0.0",
42-
"through2": "^3.0.1",
43-
"vinyl": "^2.1.0",
44-
"vinyl-file": "^3.0.0"
40+
"easy-transform-stream": "^1.0.0",
41+
"modify-filename": "^2.0.0",
42+
"plugin-error": "^2.0.1",
43+
"rev-hash": "^4.0.0",
44+
"rev-path": "^3.0.0",
45+
"sort-keys": "^5.0.0",
46+
"vinyl": "^3.0.0",
47+
"vinyl-file": "^5.0.0"
4548
},
4649
"devDependencies": {
47-
"ava": "^2.3.0",
48-
"p-event": "^4.1.0",
49-
"xo": "^0.24.0"
50+
"ava": "^5.1.0",
51+
"p-event": "^5.0.1",
52+
"xo": "^0.53.1"
5053
},
5154
"peerDependencies": {
5255
"gulp": ">=4"

‎readme.md

+20-20
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ $ npm install --save-dev gulp-rev
1616
## Usage
1717

1818
```js
19-
const gulp = require('gulp');
20-
const rev = require('gulp-rev');
19+
import gulp from 'gulp';
20+
import rev from 'gulp-rev';
2121

22-
exports.default = () => (
22+
export default () => (
2323
gulp.src('src/*.css')
2424
.pipe(rev())
2525
.pipe(gulp.dest('dist'))
@@ -83,10 +83,10 @@ The hash of each rev'd file is stored at `file.revHash`. You can use this for cu
8383
### Asset manifest
8484

8585
```js
86-
const gulp = require('gulp');
87-
const rev = require('gulp-rev');
86+
import gulp from 'gulp';
87+
import rev from 'gulp-rev';
8888

89-
exports.default = () => (
89+
export default () => (
9090
// By default, Gulp would pick `assets/css` as the base,
9191
// so we need to set it explicitly:
9292
gulp.src(['assets/css/*.css', 'assets/js/*.js'], {base: 'assets'})
@@ -110,10 +110,10 @@ An asset manifest, mapping the original paths to the revisioned paths, will be w
110110
By default, `rev-manifest.json` will be replaced as a whole. To merge with an existing manifest, pass `merge: true` and the output destination (as `base`) to `rev.manifest()`:
111111

112112
```js
113-
const gulp = require('gulp');
114-
const rev = require('gulp-rev');
113+
import gulp from 'gulp';
114+
import rev from 'gulp-rev';
115115

116-
exports.default = () => (
116+
export default () => (
117117
// By default, Gulp would pick `assets/css` as the base,
118118
// so we need to set it explicitly:
119119
gulp.src(['assets/css/*.css', 'assets/js/*.js'], {base: 'assets'})
@@ -135,12 +135,12 @@ You can optionally call `rev.manifest('manifest.json')` to give it a different p
135135
Because of the way `gulp-concat` handles file paths, you may need to set `cwd` and `path` manually on your `gulp-concat` instance to get everything to work correctly:
136136

137137
```js
138-
const gulp = require('gulp');
139-
const rev = require('gulp-rev');
140-
const sourcemaps = require('gulp-sourcemaps');
141-
const concat = require('gulp-concat');
138+
import gulp from 'gulp';
139+
import rev from 'gulp-rev';
140+
import sourcemaps from 'gulp-sourcemaps';
141+
import concat from 'gulp-concat';
142142

143-
exports.default = () => (
143+
export default () => (
144144
gulp.src('src/*.js')
145145
.pipe(sourcemaps.init())
146146
.pipe(concat({path: 'bundle.js', cwd: ''}))
@@ -163,13 +163,13 @@ Since the order of streams are not guaranteed, some plugins such as `gulp-concat
163163
This plugin does not support streaming. If you have files from a streaming source, such as Browserify, you should use [`gulp-buffer`](https://github.com/jeromew/gulp-buffer) before `gulp-rev` in your pipeline:
164164

165165
```js
166-
const gulp = require('gulp');
167-
const browserify = require('browserify');
168-
const source = require('vinyl-source-stream');
169-
const buffer = require('gulp-buffer');
170-
const rev = require('gulp-rev');
166+
import gulp from 'gulp';
167+
import browserify from 'browserify';
168+
import source from 'vinyl-source-stream';
169+
import buffer from 'gulp-buffer';
170+
import rev from 'gulp-rev';
171171

172-
exports.default = () => (
172+
export default () => (
173173
browserify('src/index.js')
174174
.bundle({debug: true})
175175
.pipe(source('index.min.js'))

‎test/_helper.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {Buffer} from 'node:buffer';
12
import Vinyl from 'vinyl';
23

34
export default function createFile({
@@ -8,13 +9,13 @@ export default function createFile({
89
revName,
910
cwd,
1011
base,
11-
contents = ''
12+
contents = '',
1213
}) {
1314
const file = new Vinyl({
1415
path,
1516
cwd,
1617
base,
17-
contents: Buffer.from(contents)
18+
contents: Buffer.from(contents),
1819
});
1920
file.revOrigPath = revOrigPath;
2021
file.revOrigBase = revOrigBase;

‎test/manifest.js

+27-23
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
import path from 'path';
1+
import {Buffer} from 'node:buffer';
2+
import path from 'node:path';
3+
import {fileURLToPath} from 'node:url';
24
import test from 'ava';
3-
import pEvent from 'p-event';
4-
import rev from '..';
5-
import createFile from './_helper';
5+
import {pEvent} from 'p-event';
6+
import rev from '../index.js';
7+
import createFile from './_helper.js';
8+
9+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
610

711
const manifestFixture = './test.manifest-fixture.json';
812
const manifestFixturePath = path.join(__dirname, manifestFixture);
@@ -14,14 +18,14 @@ test('builds a rev manifest file', async t => {
1418

1519
stream.end(createFile({
1620
path: 'unicorn-d41d8cd98f.css',
17-
revOrigPath: 'unicorn.css'
21+
revOrigPath: 'unicorn.css',
1822
}));
1923

2024
const file = await data;
2125
t.is(file.relative, 'rev-manifest.json');
2226
t.deepEqual(
2327
JSON.parse(file.contents.toString()),
24-
{'unicorn.css': 'unicorn-d41d8cd98f.css'}
28+
{'unicorn.css': 'unicorn-d41d8cd98f.css'},
2529
);
2630
});
2731

@@ -32,7 +36,7 @@ test('allows naming the manifest file', async t => {
3236

3337
stream.end(createFile({
3438
path: 'unicorn-d41d8cd98f.css',
35-
revOrigPath: 'unicorn.css'
39+
revOrigPath: 'unicorn.css',
3640
}));
3741

3842
const file = await data;
@@ -42,13 +46,13 @@ test('allows naming the manifest file', async t => {
4246
test('appends to an existing rev manifest file', async t => {
4347
const stream = rev.manifest({
4448
path: manifestFixturePath,
45-
merge: true
49+
merge: true,
4650
});
4751
const data = pEvent(stream, 'data');
4852

4953
stream.end(createFile({
5054
path: 'unicorn-d41d8cd98f.css',
51-
revOrigPath: 'unicorn.css'
55+
revOrigPath: 'unicorn.css',
5256
}));
5357

5458
const file = await data;
@@ -57,8 +61,8 @@ test('appends to an existing rev manifest file', async t => {
5761
JSON.parse(file.contents.toString()),
5862
{
5963
'app.js': 'app-a41d8cd1.js',
60-
'unicorn.css': 'unicorn-d41d8cd98f.css'
61-
}
64+
'unicorn.css': 'unicorn-d41d8cd98f.css',
65+
},
6266
);
6367
});
6468

@@ -68,37 +72,37 @@ test('does not append to an existing rev manifest by default', async t => {
6872

6973
stream.end(createFile({
7074
path: 'unicorn-d41d8cd98f.css',
71-
revOrigPath: 'unicorn.css'
75+
revOrigPath: 'unicorn.css',
7276
}));
7377

7478
const file = await data;
7579
t.is(file.relative, manifestFixtureRelative);
7680
t.deepEqual(
7781
JSON.parse(file.contents.toString()),
78-
{'unicorn.css': 'unicorn-d41d8cd98f.css'}
82+
{'unicorn.css': 'unicorn-d41d8cd98f.css'},
7983
);
8084
});
8185

8286
test('sorts the rev manifest keys', async t => {
8387
const stream = rev.manifest({
8488
path: manifestFixturePath,
85-
merge: true
89+
merge: true,
8690
});
8791
const data = pEvent(stream, 'data');
8892

8993
stream.write(createFile({
9094
path: 'unicorn-d41d8cd98f.css',
91-
revOrigPath: 'unicorn.css'
95+
revOrigPath: 'unicorn.css',
9296
}));
9397
stream.end(createFile({
9498
path: 'pony-d41d8cd98f.css',
95-
revOrigPath: 'pony.css'
99+
revOrigPath: 'pony.css',
96100
}));
97101

98102
const file = await data;
99103
t.deepEqual(
100104
Object.keys(JSON.parse(file.contents.toString())),
101-
['app.js', 'pony.css', 'unicorn.css']
105+
['app.js', 'pony.css', 'unicorn.css'],
102106
);
103107
});
104108

@@ -113,7 +117,7 @@ test('respects directories', async t => {
113117
revOrigPath: path.join(__dirname, 'foo', 'unicorn.css'),
114118
revOrigBase: __dirname,
115119
origName: 'unicorn.css',
116-
revName: 'unicorn-d41d8cd98f.css'
120+
revName: 'unicorn-d41d8cd98f.css',
117121
}));
118122
stream.end(createFile({
119123
cwd: __dirname,
@@ -122,7 +126,7 @@ test('respects directories', async t => {
122126
revOrigBase: __dirname,
123127
revOrigPath: path.join(__dirname, 'bar', 'pony.css'),
124128
origName: 'pony.css',
125-
revName: 'pony-d41d8cd98f.css'
129+
revName: 'pony-d41d8cd98f.css',
126130
}));
127131

128132
const MANIFEST = {};
@@ -146,7 +150,7 @@ test('respects files coming from directories with different bases', async t => {
146150
revOrigBase: path.join(__dirname, 'vendor1'),
147151
revOrigPath: path.join(__dirname, 'vendor1', 'foo', 'scriptfoo.js'),
148152
origName: 'scriptfoo.js',
149-
revName: 'scriptfoo-d41d8cd98f.js'
153+
revName: 'scriptfoo-d41d8cd98f.js',
150154
}));
151155
stream.end(createFile({
152156
cwd: __dirname,
@@ -155,7 +159,7 @@ test('respects files coming from directories with different bases', async t => {
155159
revOrigBase: path.join(__dirname, 'vendor2'),
156160
revOrigPath: path.join(__dirname, 'vendor2', 'bar', 'scriptbar.js'),
157161
origName: 'scriptfoo.js',
158-
revName: 'scriptfoo-d41d8cd98f.js'
162+
revName: 'scriptfoo-d41d8cd98f.js',
159163
}));
160164

161165
const MANIFEST = {};
@@ -175,13 +179,13 @@ test('uses correct base path for each file', async t => {
175179
cwd: 'app/',
176180
base: 'app/',
177181
path: path.join('app', 'foo', 'scriptfoo-d41d8cd98f.js'),
178-
revOrigPath: 'scriptfoo.js'
182+
revOrigPath: 'scriptfoo.js',
179183
}));
180184
stream.end(createFile({
181185
cwd: '/',
182186
base: 'assets/',
183187
path: path.join('/assets', 'bar', 'scriptbar-d41d8cd98f.js'),
184-
revOrigPath: 'scriptbar.js'
188+
revOrigPath: 'scriptbar.js',
185189
}));
186190

187191
const MANIFEST = {};

‎test/rev.js

+52-34
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import path from 'path';
1+
import path from 'node:path';
22
import test from 'ava';
3-
import pEvent from 'p-event';
4-
import rev from '..';
5-
import createFile from './_helper';
3+
import {pEvent, pEventIterator} from 'p-event';
4+
import rev from '../index.js';
5+
import createFile from './_helper.js';
66

77
test('revs files', async t => {
88
const stream = rev();
99
const data = pEvent(stream, 'data');
1010

1111
stream.end(createFile({
12-
path: 'unicorn.css'
12+
path: 'unicorn.css',
1313
}));
1414

1515
const file = await data;
@@ -22,7 +22,7 @@ test('adds the revision hash before the first `.` in the filename', async t => {
2222
const data = pEvent(stream, 'data');
2323

2424
stream.end(createFile({
25-
path: 'unicorn.css.map'
25+
path: 'unicorn.css.map',
2626
}));
2727

2828
const file = await data;
@@ -35,7 +35,7 @@ test('stores the hashes for later', async t => {
3535
const data = pEvent(stream, 'data');
3636

3737
stream.end(createFile({
38-
path: 'unicorn.css'
38+
path: 'unicorn.css',
3939
}));
4040

4141
const file = await data;
@@ -44,72 +44,90 @@ test('stores the hashes for later', async t => {
4444
t.is(file.revHash, 'd41d8cd98f');
4545
});
4646

47-
test.cb('handles sourcemaps transparently', t => {
47+
test('handles sourcemaps transparently', async t => {
4848
const stream = rev();
49-
50-
stream.on('data', file => {
51-
if (path.extname(file.path) === '.map') {
52-
t.is(file.path, path.normalize('maps/pastissada-d41d8cd98f.css.map'));
53-
t.end();
54-
}
49+
const data = pEventIterator(stream, 'data', {
50+
resolutionEvents: ['finish'],
5551
});
5652

5753
stream.write(createFile({
58-
path: 'pastissada.css'
54+
path: 'pastissada.css',
5955
}));
6056

6157
stream.end(createFile({
6258
path: 'maps/pastissada.css.map',
63-
contents: JSON.stringify({file: 'pastissada.css'})
59+
contents: JSON.stringify({file: 'pastissada.css'}),
6460
}));
65-
});
66-
67-
test.cb('handles unparseable sourcemaps correctly', t => {
68-
const stream = rev();
6961

70-
stream.on('data', file => {
62+
let sourcemapCount = 0;
63+
for await (const file of data) {
7164
if (path.extname(file.path) === '.map') {
72-
t.is(file.path, 'pastissada-d41d8cd98f.css.map');
73-
t.end();
65+
t.is(file.path, path.normalize('maps/pastissada-d41d8cd98f.css.map'));
66+
sourcemapCount++;
7467
}
68+
}
69+
70+
t.is(sourcemapCount, 1);
71+
});
72+
73+
test('handles unparseable sourcemaps correctly', async t => {
74+
const stream = rev();
75+
const data = pEventIterator(stream, 'data', {
76+
resolutionEvents: ['finish'],
7577
});
7678

7779
stream.write(createFile({
78-
path: 'pastissada.css'
80+
path: 'pastissada.css',
7981
}));
8082

8183
stream.end(createFile({
8284
path: 'pastissada.css.map',
83-
contents: 'Wait a minute, this is invalid JSON!'
85+
contents: 'Wait a minute, this is invalid JSON!',
8486
}));
85-
});
86-
87-
test.cb('okay when the optional sourcemap.file is not defined', t => {
88-
const stream = rev();
8987

90-
stream.on('data', file => {
88+
let sourcemapCount = 0;
89+
for await (const file of data) {
9190
if (path.extname(file.path) === '.map') {
9291
t.is(file.path, 'pastissada-d41d8cd98f.css.map');
93-
t.end();
92+
sourcemapCount++;
9493
}
94+
}
95+
96+
t.is(sourcemapCount, 1);
97+
});
98+
99+
test('okay when the optional sourcemap.file is not defined', async t => {
100+
const stream = rev();
101+
const data = pEventIterator(stream, 'data', {
102+
resolutionEvents: ['finish'],
95103
});
96104

97105
stream.write(createFile({
98-
path: 'pastissada.css'
106+
path: 'pastissada.css',
99107
}));
100108

101109
stream.end(createFile({
102110
path: 'pastissada.css.map',
103-
contents: JSON.stringify({})
111+
contents: JSON.stringify({}),
104112
}));
113+
114+
let sourcemapCount = 0;
115+
for await (const file of data) {
116+
if (path.extname(file.path) === '.map') {
117+
t.is(file.path, 'pastissada-d41d8cd98f.css.map');
118+
sourcemapCount++;
119+
}
120+
}
121+
122+
t.is(sourcemapCount, 1);
105123
});
106124

107125
test('handles a `.` in the folder name', async t => {
108126
const stream = rev();
109127
const data = pEvent(stream, 'data');
110128

111129
stream.end(createFile({
112-
path: 'mysite.io/unicorn.css'
130+
path: 'mysite.io/unicorn.css',
113131
}));
114132

115133
const file = await data;

0 commit comments

Comments
 (0)
Please sign in to comment.