1

Whilst this question is related to Workbox and Webpack, it does not require any prior knowledge of either library.

Background (skip if not familiar with Workbox)

I am currently utilising the InjectManifest plugin from Workbox 4.3.1 (workbox-webpack-plugin). This version of the library offers an option called manifestTransforms, but unfortunately, the transformations are not applied to assets within the webpack compilation (this is a known issue).

Whilst this has been fixed in Workbox v5+, I am unable to upgrade due to another library in my build process requiring webpack v3 (Dynamic Importing in Laravel Mix)

The reason I mention the above is because unforunately the solution is not to upgrade to workbox v5+.

The Problem

I have an auto-generated file that looks like this:

self.__precacheManifest = (self.__precacheManifest || []).concat([
    {
        "revision": "68cd3870a6400d76a16c",
        "url": "//css/app.css"
    },
    // etc...
]);

I need to somehow extract the the contents of the object stored within self.__precacheManifest, apply my own transformations, and then save it back to the file.

What I have Tried...

This is as far as I have got:

// As the precached filename is hashed, we need to read the
// directory in order to find the filename. Assuming there
// are no other files called `precache-manifest`, we can assume
// it is the first value in the filtered array. There is no
// need to test if [0] has a value because if it doesn't
// this needs to throw an error
let manifest = fs
    .readdirSync(path.normalize(`${__dirname}/dist/js`))
    .filter(filename => filename.startsWith('precache-manifest'))[0];

require('./dist/js/' + manifest);

// This does not fire because of thrown error...
console.log(self.__precacheManifest);

This throws the following error:

self is not defined

I understand why it is throwing the error, but I have no idea how I am going to get around this because I need to somehow read the contents of the file in order to extract the object. Can anyone advise me here?

Bear in mind that once I have applied the transformations to the object, I then need to save the updated object to the file...

Ben Carey
  • 14,734
  • 16
  • 77
  • 155
  • what is `self` supposed to be in your js script? – Chris Rollins Sep 01 '19 at 00:10
  • @ChrisRollins - `self` will refer to `window` because the precache file is only meant to be used clientside. I have no control over the initial contents of the precache file, it is auto generated by the `InjectManifest` plugin – Ben Carey Sep 01 '19 at 00:12
  • correct me if I'm wrong but I think you want `this` – Chris Rollins Sep 01 '19 at 00:13
  • @ChrisRollins - as mentioned, I have no control over that file, it is auto generated by the plugin. `self` is different to `this`. See here: https://stackoverflow.com/questions/16875767/difference-between-this-and-self-in-javascript/38549303 – Ben Carey Sep 01 '19 at 00:14
  • oh my bad I see you're actually running the generated script with `require` so the problem is you're running code that only works on browser in node. maybe just bind `self` to the global object before your `require` statement – Chris Rollins Sep 01 '19 at 00:18
  • @ChrisRollins - I am aware that `self` does not work in Node. The problem is, the contents of this file is automatically generated by a plugin so I literally have no control over the initial state – Ben Carey Sep 01 '19 at 00:18
  • @ChrisRollins - I already tried this by putting `let self;` above the require statement but it still threw the error – Ben Carey Sep 01 '19 at 00:19
  • well if you just have `let self;` it will still be undefined... how about `let self = global;` ? – Chris Rollins Sep 01 '19 at 00:20
  • @ChrisRollins - good idea!!! I'll try that now :-D – Ben Carey Sep 01 '19 at 00:20
  • @ChrisRollins - negative :-(. I tried this: `let self = global;` followed by this `require('./dist/js/' + filename);` and still get the same error – Ben Carey Sep 01 '19 at 00:23
  • @ChrisRollins - I think my only option is to read the file to a string using `fs.readFileSync`, extracting the object using a regular expression, applying transformations, and then saving it all back to the file as a string. – Ben Carey Sep 01 '19 at 00:24
  • Oh of course. node doesn't bring in the scope from your script into the file you pass to `require` so I think you're going to have to do something kind of hacky – Chris Rollins Sep 01 '19 at 00:25
  • another thing you could do is take the source code as a string, insert `let self = global;` at the start, then save it into a temp file, then require the temp file? lol – Chris Rollins Sep 01 '19 at 00:26
  • @ChrisRollins - that might be better than my solution actually... Not particularly keen on trying to extract an object from a string of javascript, I don't imagine the regex will be that pretty... – Ben Carey Sep 01 '19 at 00:27
  • @ChrisRollins - I could just replace `self.__precacheManifest = (self.__precacheManifest || []).concat(` and the closing `);`, and then read the string as JSON but it feels a bit hacky. I guess the precache code isn't going to change so it is at least safe... – Ben Carey Sep 01 '19 at 00:30

1 Answers1

2

Since self refers to window and window does not exist in node.js a way around is needed. One thing that should work is to define the variable self in Node's global scope and let the require statement populate the content of the variable, like this:

global['self'] = {};
require('./dist/js/' + manifest);
console.log(self.__precacheManifest);

To save the modified contents back to the file

const newPrecacheManifest = JSON.stringify(updatedArray);
fs.writeFileSync('./dist/js/' + manifest, `self.__precacheManifest = (self.__precacheManifest || []).concat(${newPrecachedManifes});`, 'utf8');
dmbarreiro
  • 120
  • 2
  • 8
  • This is brilliant! Thank you! However, it hasn't actually answered the question as it only gets me halfway there. How would you then save the amended contents back to the file? – Ben Carey Sep 01 '19 at 00:48
  • 1
    I assume I will just have to use something like `fs.writeFile('./dist/js/' + manifest, 'self.__precacheManifest = (self._...' + JSON.stringify(some_object) + ');'., 'utf8');` – Ben Carey Sep 01 '19 at 00:56
  • I can confirm my above comment works - feel free to update your answer to include this and I will accept it :-D – Ben Carey Sep 01 '19 at 01:04
  • Oh, sorry, didn't remember that part. What you propose is great. Just to give another option you could use the synchronous version of writeFile as well `const stringManifest = JSON.stringify(self.__precacheManifest); fs.writeFileSync( './dist/js/' + manifest + '.test', `self.__precacheManifest = ${stringManifest}`, 'utf8');` I'll update my answer – dmbarreiro Sep 01 '19 at 01:07