diff --git a/package.json b/package.json index 85133a009..c6a2d6529 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,6 @@ "@types/eslint": "^8.56.10", "@types/estree": "^1.0.5", "@types/fs-extra": "^9.0.13", - "@types/lodash": "^4.17.0", "@types/node": "^20.14.8", "@types/stringify-object": "^3.3.1", "@typescript-eslint/eslint-plugin": "^5.62.0", diff --git a/packages/workbox-build/package-lock.json b/packages/workbox-build/package-lock.json index a7c50e542..9820ef107 100644 --- a/packages/workbox-build/package-lock.json +++ b/packages/workbox-build/package-lock.json @@ -20,10 +20,10 @@ "@trickfilm400/rollup-plugin-off-main-thread": "^3.0.0-pre1", "ajv": "^8.6.0", "common-tags": "^1.8.0", + "eta": "^4.5.1", "fast-json-stable-stringify": "^2.1.0", "fs-extra": "^9.0.1", "glob": "^11.0.1", - "lodash": "^4.17.20", "pretty-bytes": "^5.3.0", "rollup": "^4.53.3", "source-map": "^0.8.0-beta.0", @@ -2481,6 +2481,18 @@ "node": ">=0.10.0" } }, + "node_modules/eta": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/eta/-/eta-4.5.1.tgz", + "integrity": "sha512-EaNCGm+8XEIU7YNcc+THptWAO5NfKBHHARxt+wxZljj9bTr/+arRoOm9/MpGt4n6xn9fLnPFRSoLD0WFYGFUxQ==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/bgub/eta?sponsor=1" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6586,6 +6598,11 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" }, + "eta": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/eta/-/eta-4.5.1.tgz", + "integrity": "sha512-EaNCGm+8XEIU7YNcc+THptWAO5NfKBHHARxt+wxZljj9bTr/+arRoOm9/MpGt4n6xn9fLnPFRSoLD0WFYGFUxQ==" + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", diff --git a/packages/workbox-build/package.json b/packages/workbox-build/package.json index 30e7d59ac..645769f9b 100644 --- a/packages/workbox-build/package.json +++ b/packages/workbox-build/package.json @@ -34,10 +34,10 @@ "@trickfilm400/rollup-plugin-off-main-thread": "^3.0.0-pre1", "ajv": "^8.6.0", "common-tags": "^1.8.0", + "eta": "^4.5.1", "fast-json-stable-stringify": "^2.1.0", "fs-extra": "^9.0.1", "glob": "^11.0.1", - "lodash": "^4.17.20", "pretty-bytes": "^5.3.0", "rollup": "^4.53.3", "source-map": "^0.8.0-beta.0", diff --git a/packages/workbox-build/src/lib/populate-sw-template.ts b/packages/workbox-build/src/lib/populate-sw-template.ts index 0f00e9858..57776dcc9 100644 --- a/packages/workbox-build/src/lib/populate-sw-template.ts +++ b/packages/workbox-build/src/lib/populate-sw-template.ts @@ -6,7 +6,7 @@ https://opensource.org/licenses/MIT. */ -import template from 'lodash/template'; +import {Eta} from 'eta'; import {errors} from './errors'; import {GeneratePartial, ManifestEntry} from '../types'; @@ -15,6 +15,11 @@ import {runtimeCachingConverter} from './runtime-caching-converter'; import {stringifyWithoutComments} from './stringify-without-comments'; import {swTemplate} from '../templates/sw-template'; +const eta = new Eta({ + useWith: true, + autoEscape: false, +}) + export function populateSWTemplate({ cacheId, cleanupOutdatedCaches, @@ -72,7 +77,7 @@ export function populateSWTemplate({ const moduleRegistry = new ModuleRegistry(); try { - const populatedTemplate = template(swTemplate)({ + const populatedTemplate = eta.renderString(swTemplate)({ cacheId, cleanupOutdatedCaches, clientsClaim, diff --git a/test/workbox-build/node/lib/populate-sw-template.js b/test/workbox-build/node/lib/populate-sw-template.js index 3d5e88b7c..6c01b81fc 100644 --- a/test/workbox-build/node/lib/populate-sw-template.js +++ b/test/workbox-build/node/lib/populate-sw-template.js @@ -19,9 +19,14 @@ describe(`[workbox-build] lib/populate-sw-template.js`, function () { it(`should throw an error if templating fails`, function () { const manifestEntries = ['ignored']; + // Mock the Eta class and its renderString method to throw an error const {populateSWTemplate} = proxyquire(MODULE_PATH, { - 'lodash/template': () => { - throw new Error(); + 'eta': { + Eta: class { + renderString() { + throw new Error(); + } + } }, }); @@ -35,7 +40,11 @@ describe(`[workbox-build] lib/populate-sw-template.js`, function () { it(`should throw an error if both manifestEntries and runtimeCaching are empty`, function () { const {populateSWTemplate} = proxyquire(MODULE_PATH, { - 'lodash/template': () => {}, + 'eta': { + Eta: class { + renderString() {} + } + }, }); try { @@ -54,10 +63,16 @@ describe(`[workbox-build] lib/populate-sw-template.js`, function () { const precacheOptionsString = '{}'; const manifestEntries = ['ignored']; - const innerStub = sinon.stub().returns(''); - const outerStub = sinon.stub().returns(innerStub); + // Create a single stub to simulate renderString + const renderStringStub = sinon.stub().returns(''); const {populateSWTemplate} = proxyquire(MODULE_PATH, { - 'lodash/template': outerStub, + 'eta': { + Eta: class { + constructor() { + this.renderString = renderStringStub; + } + } + }, './runtime-caching-converter': { runtimeCachingConverter: () => runtimeCachingPlaceholder, }, @@ -66,30 +81,30 @@ describe(`[workbox-build] lib/populate-sw-template.js`, function () { populateSWTemplate({manifestEntries}); - expect(outerStub.alwaysCalledWith(swTemplate)).to.be.true; - - // Doing a strict comparison with functions isn't easy. - expect(innerStub.args[0][0].use).to.be.a('function'); - delete innerStub.args[0][0].use; - - expect(innerStub.args[0]).to.eql([ - { - manifestEntries, - cacheId: undefined, - cleanupOutdatedCaches: undefined, - clientsClaim: undefined, - disableDevLogs: undefined, - importScripts: undefined, - navigateFallback: undefined, - navigateFallbackDenylist: undefined, - navigateFallbackAllowlist: undefined, - navigationPreload: undefined, - offlineAnalyticsConfigString: undefined, - precacheOptionsString, - runtimeCaching: runtimeCachingPlaceholder, - skipWaiting: undefined, - }, - ]); + // Eta receives the template as the first argument: args[0][0] + expect(renderStringStub.args[0][0]).to.equal(swTemplate); + + // The data is passed as the second argument: args[0][1] + expect(renderStringStub.args[0][1].use).to.be.a('function'); + delete renderStringStub.args[0][1].use; + + // Compare the data object directly + expect(renderStringStub.args[0][1]).to.eql({ + manifestEntries, + cacheId: undefined, + cleanupOutdatedCaches: undefined, + clientsClaim: undefined, + disableDevLogs: undefined, + importScripts: undefined, + navigateFallback: undefined, + navigateFallbackDenylist: undefined, + navigateFallbackAllowlist: undefined, + navigationPreload: undefined, + offlineAnalyticsConfigString: undefined, + precacheOptionsString, + runtimeCaching: runtimeCachingPlaceholder, + skipWaiting: undefined, + }); }); it(`should pass the expected options to the template`, function () { @@ -115,14 +130,15 @@ describe(`[workbox-build] lib/populate-sw-template.js`, function () { const precacheOptionsString = '{\n "directoryIndex": "index.html",\n "ignoreURLParametersMatching": [/a/, /b/]\n}'; - // There are two stages in templating: creating the active template function - // from an initial string, and passing variables to that template function - // to get back a final, populated template string. - // We need to stub out both of those steps to test the full flow. - const templatePopulationStub = sinon.stub().returns(''); - const templateCreationStub = sinon.stub().returns(templatePopulationStub); + const renderStringStub = sinon.stub().returns(''); const {populateSWTemplate} = proxyquire(MODULE_PATH, { - 'lodash/template': templateCreationStub, + 'eta': { + Eta: class { + constructor() { + this.renderString = renderStringStub; + } + } + }, './runtime-caching-converter': { runtimeCachingConverter: () => runtimeCachingPlaceholder, }, @@ -148,30 +164,30 @@ describe(`[workbox-build] lib/populate-sw-template.js`, function () { skipWaiting, }); - expect(templateCreationStub.alwaysCalledWith(swTemplate)).to.be.true; - - // Doing a strict comparison with functions isn't easy. - expect(templatePopulationStub.args[0][0].use).to.be.a('function'); - delete templatePopulationStub.args[0][0].use; - - expect(templatePopulationStub.args[0]).to.eql([ - { - cacheId, - cleanupOutdatedCaches, - clientsClaim, - disableDevLogs, - importScripts, - manifestEntries, - navigateFallback, - navigateFallbackDenylist, - navigateFallbackAllowlist, - navigationPreload, - offlineAnalyticsConfigString, - runtimeCaching: runtimeCachingPlaceholder, - precacheOptionsString, - skipWaiting, - }, - ]); + // Eta receives the template as the first argument: args[0][0] + expect(renderStringStub.args[0][0]).to.equal(swTemplate); + + // The data is passed as the second argument: args[0][1] + expect(renderStringStub.args[0][1].use).to.be.a('function'); + delete renderStringStub.args[0][1].use; + + // Compare the data object directly + expect(renderStringStub.args[0][1]).to.eql({ + cacheId, + cleanupOutdatedCaches, + clientsClaim, + disableDevLogs, + importScripts, + manifestEntries, + navigateFallback, + navigateFallbackDenylist, + navigateFallbackAllowlist, + navigationPreload, + offlineAnalyticsConfigString, + runtimeCaching: runtimeCachingPlaceholder, + precacheOptionsString, + skipWaiting, + }); }); it(`should handle a complex offlineGoogleAnalytics value when populating the template`, function () { @@ -190,10 +206,15 @@ describe(`[workbox-build] lib/populate-sw-template.js`, function () { const offlineAnalyticsConfigString = `{\n\tparameterOverrides: {\n\t\tcd1: 'offline'\n\t},\n\thitFilter: (params) => {\n \n params.set('cm1', params.get('qt'));\n }\n}`; const manifestEntries = ['ignored']; - const innerStub = sinon.stub().returns(''); - const outerStub = sinon.stub().returns(innerStub); + const renderStringStub = sinon.stub().returns(''); const {populateSWTemplate} = proxyquire(MODULE_PATH, { - 'lodash/template': outerStub, + 'eta': { + Eta: class { + constructor() { + this.renderString = renderStringStub; + } + } + }, './runtime-caching-converter': { runtimeCachingConverter: () => runtimeCachingPlaceholder, }, @@ -202,29 +223,26 @@ describe(`[workbox-build] lib/populate-sw-template.js`, function () { populateSWTemplate({manifestEntries, offlineGoogleAnalytics}); - expect(outerStub.alwaysCalledWith(swTemplate)).to.be.true; - - // Doing a strict comparison with functions isn't easy. - expect(innerStub.args[0][0].use).to.be.a('function'); - delete innerStub.args[0][0].use; - - expect(innerStub.args[0]).to.eql([ - { - manifestEntries, - cacheId: undefined, - cleanupOutdatedCaches: undefined, - clientsClaim: undefined, - disableDevLogs: undefined, - importScripts: undefined, - navigateFallback: undefined, - navigateFallbackDenylist: undefined, - navigateFallbackAllowlist: undefined, - navigationPreload: undefined, - offlineAnalyticsConfigString, - precacheOptionsString, - runtimeCaching: runtimeCachingPlaceholder, - skipWaiting: undefined, - }, - ]); + expect(renderStringStub.args[0][0]).to.equal(swTemplate); + + expect(renderStringStub.args[0][1].use).to.be.a('function'); + delete renderStringStub.args[0][1].use; + + expect(renderStringStub.args[0][1]).to.eql({ + manifestEntries, + cacheId: undefined, + cleanupOutdatedCaches: undefined, + clientsClaim: undefined, + disableDevLogs: undefined, + importScripts: undefined, + navigateFallback: undefined, + navigateFallbackDenylist: undefined, + navigateFallbackAllowlist: undefined, + navigationPreload: undefined, + offlineAnalyticsConfigString, + precacheOptionsString, + runtimeCaching: runtimeCachingPlaceholder, + skipWaiting: undefined, + }); }); });