Use browser.js in a service worker - ecmascript-6

We want to use a Service Worker to perform client-side source code transformation for development purposes. We want to use Babel to transpile ES6+/ES2015 files to ES5 modules.
However, including the browser version of babel in a Service Worker using importScripts causes the following errors:
GET http://localhost:8080/babel-core/browser.js net::ERR_FAILED
Uncaught NetworkError: Failed to execute 'importScripts' on 'WorkerGlobalScope': The script at 'http://localhost:8080/babel-core/browser.js' failed to load.
So, the question is, how to correctly import babel into a Service Worker.
edit: This is not the obvious NetworkError, as we can change the content of the file into something simple, which enables us to actually load and execute the file. Also, the file can be loaded with a normal <script> tag.
edit2: To get this message, check out this repository https://github.com/onsetsu/lively4-core.git, start a local server at port 8080 and finally load http://localhost:8080/bootworker.html. We are currently using Chrome 44.

How about my experiment here https://github.com/bahmutov/babel-service - you can see the demo at https://babel-service-demo.herokuapp.com/.
I am using feature tests to detect supported features and transpile the intercepted code selectively. Of course this is just a start and only maps default parameters to babel plugins, but more features could be mapped.
Also, the people behind feature tests are discussing the selective transpile https://github.com/getify/es-feature-tests/issues/9

As a general rule, using a service worker for something crucial for a site's functionality isn't a recommended practice. Service workers are intended to be a progressive enhancement, and your sites should be designed to still be functional if the associated service worker isn't available.
Even in browsers which support service workers, there might not be one controlling your page if a user shift-reloads or if it's the very first navigation, before the service worker has had a chance to take control.
To answer your specific question, the ServiceWorkerGlobalScope under which service worker code executes exposes different functionality vs. a normal page's global scope, and it would appear that something in the browser.js script you're trying to import assumes functionality that's only available in a normal page. Unfortunately, Chrome's DevTools, even with the debugger enabled, doesn't reveal which specific statement is causing the error, so I can't say which exact statement(s) are invalid.

Related

Embed create-react-app in dev mode on another site

I'm developing a Wordpress "widget" that is going to be a little react app. I've chosen create-react-app for this purpose.
Now I can see how to run the development server standalone easily enough, but I'd like to develop it while it sits inside the Wordpress website. I've created a trivial "Custom HTML" widget:
<div id="root"></div>
<script type="text/javascript" src="http://localhost:8080/static/js/bundle.js"></script>
This does not seem to work however...
Note I came up with /static/js/bundle.js by looking at the requests in the network tab when loading http://localhost:8080 directly, which is the prescribed way to access the dev version of the app.
So how do I access the development version of the app (with all the live reloading goodness) while embedded on my local version of the Wordpress site?
I had this same problem today in a PHP app I am developing. It is very frustrating to embed a create-react-app in development mode, and I had to consult a lot of different resources to learn how to do so. Here is a summary of my findings.
Using an iframe
Using an iframe to embed the create-react-app, as #quickshiftin suggests, is not a bad idea, but if you wish to pass configuration to the embedded SPA by calling methods or setting global variables in Javascript, it will not work* -- as the MDN documentation says (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#scripting), iframes are subject to the same-origin policy.
* (Note: I found out after writing most of this answer that there is indeed a way to bypass the same-origin policy. It's called Window.postMessage(), and it's also mentioned in the section of the MDN documentation that I linked above. You may want to consider using that. But if you would like to avoid using an iframe for whatever reason, read on :)
Create-React-App file structure; embedding in production mode
The first thing you must know is that embedding bundle.js is not enough -- create-react-app builds multiple JS files that need to be embedded using <script> tags in the correct order. This blog post by Jeremiah Tabb describes the file structure of the bundled code and suggests a way to embed the create-react-app in production: https://betterprogramming.pub/how-to-embed-a-react-application-on-any-website-1bee1d15617f
The filenames of the bundled code contain hashes which change at every build. The hashing can't be disabled, it's a WONTFIX in create-react-app (https://github.com/facebook/create-react-app/issues/821). So, to get the bundled js filenames for a production build, in PHP, you can just traverse the build/static/js directory and output one <script> tag per .js file you find. (It may be wasteful to always request all chunks, but I haven't yet taken the time to look into the right way to do it.)
Development mode looks for chunks under the wrong path
But in development mode, which is your actual question, it is handled a bit differently. The index.html served by the dev server only loads three scripts initially: bundle.js, vendors~main.chunk.js and main.chunk.js. The other chunks are loaded dynamically. You can try embedding those three scripts on your Wordpress page, but you will find that at runtime, the 'bootstrap' code generated by Webpack looks for the chunks at the wrong URL, using e.g. localhost instead of localhost:3000, resulting in a chunk loading error.
PUBLIC_URL and "homepage" don't work in development mode
According to the Create-React-App documentation and various other answers on this site, you're supposed to be able to use the environment variable PUBLIC_URL or the key "homepage" in package.json to override the hostname and port where the JS code is served so that the chunks will load, but these settings don't do anything in development mode. This is an open issue in create-react-app: https://github.com/facebook/create-react-app/issues/9001
Workaround using npx patch-package
You might think you are in trouble and will have to eject your project and modify the webpack configuration yourself to get this working. But fortunately, there is a workaround described here in a comment by SergeyVolynkin which solves the problem without ejecting, using npx patch-package to patch react-dev-utils:
https://github.com/facebook/create-react-app/issues/9001#issuecomment-838370686
What SergeyVolynkin does not mention is that, after creating the patch and checking it into VCS, you should set up patch-package in your package.json so that the patches will be applied by npm / yarn when you run yarn / npm install. See the documentation for patch-package here: https://github.com/ds300/patch-package#set-up
Summary
After applying SergeyVolynkin's patch, I was able to get the development build embedded in my PHP app. I used the following scripts in my package.json:
"scripts": {
"start": "PORT=1234 PUBLIC_URL=http://localhost:1234 WDS_SOCKET_PORT=1234 react-scripts start",
"postinstall": "patch-package"
}
And I used the following lines in the HTML served by my PHP app:
<script src="http://localhost:1234/static/js/bundle.js"></script>
<script src="http://localhost:1234/static/js/vendors~main.chunk.js"></script>
<script src="http://localhost:1234/static/js/main.chunk.js"></script>
By doing this, I could embed an app created using create-react-app in dev mode in my PHP app.

Chrome remote debugging protocol, getAllStyleSheets() method not found

I am trying to access all stylesheets from a Chrome extension using the Remote debugging protocol. According to https://chromedevtools.github.io/debugger-protocol-viewer/CSS/ there should be a method for this:
A client can also discover all the existing stylesheets with the
getAllStyleSheets() method
However I was unable to execute this. Is this implemented? Is there another way of accessing the CSSOM from a background script
It was implemented, but has been since removed.
DevTools: [CSS] remove getAllStylesheets method from protocol
This patch removes getAllStylesheets method from protocol. Instead,
clients should track styleSheetAdded/styleSheetRemoved events to
maintain actual stylesheet set.

ServiceWorker: Browser not detecting new version

I'm experimenting with the HTML5 ServiceWorker API based on this article. In the article it is mentioned that
When the user navigates to your site, the browser tries to redownload
the script file that defined the service worker in the background. If
there is even a byte's difference in the service worker file compared
to what it currently has, it considers it 'new'.
From which I conclude that if I would change anything in the worker's script file, it would prompt the browser to define a new version that would kick in when all pages referencing the old version of the worker are terminated.
Edit: Apparently the browser is caching the serviceworker.js file itself, which is why new versions aren't picked up. Could anyone tell me how to avoid caching the worker file? I've looked through the available demo's online (including those on MDN and W3C Webmob's GitHub)
This is my file structure:
|- index.html
|- serviceworker.js // the actual worker
|- serviceworker-cache-polyfill.js
|- serviceworker-registration.js // contains the registration logic for the worker
|- style.css
I configured my cache to include following URLs:
"/style.css"
The issue was not the configuration of the ServiceWorker, but the fact my server cached the file. Can't say I don't feel stupid I didn't checked this earlier.
For future reference, I am using http-server, it caches by default all files for 1 hour. You can override this by passing in the c parameter. To disable caching altogether, pass in -1:
http-server -c-1
Edit The following article contains a good summary on how to develop with the ServiceWorker:
In order to guarantee that the latest version of your Service Worker
script is being used, follow these instructions:
Configure your local server to serve your Service Worker script as non-cacheable (cache-control: no-cache)
After you made changes to your service
worker script:
close all but one of the tabs pointing to your web application
hit shift-reload to bypass the service worker as to ensure
that the remaining tab isn't under the control of a service worker
hit
reload to let the newer version of the Service Worker control the
page.
That would indeed explain the behavior. The update logic does respect the HTTP cache control header but up to 24 hours (to avoid being stuck with a broken SW served with a Cache-control: 1 year header).

Chrome Extension Internals

I am trying to understand the internals of Chrome Extension. It will be great if you can provide more details regarding this.
My understandings are as follows:
Extensions run in its own process.
Extensions interact with Browser process through IPC.
My question:
1. Whenever we call a extension api such as chrome.module.function() from extension user script, I would expect the function to go to the renderer process first and then send a IPC such as ExtensionHostMsg_ABC to the browser process to do the required operation.
Extension Process Browser Process
ExtensionHostMsg_Req
chrome.module.function() ----------------> Handle the msg
ExtensionMsg_Response
Send the result back to js <-----------------
But to my surprise, I see that all the extension api functions are declared and defined in chrome/browser/extensions/xyz_api.h & .cc files. So it looks like when a api is called from js, we come directly to the browser process. Where and how does this magic happens?
I also see some extensions bindings c++ code in chrome/renderer/extensions/abc_bindings.cc and the corresponding js code in chrome/renderer/extensions/resources/abc.js. I think we have this code to share some information between the extension renderer and js code. Is it right? Can you provide me a example scenario for this?
Sorry for asking these basic questions. I really appreciate your help.
Regarding your question on how the bindings are made. You define your extension API in chrome/common/extensions/api/extension_api.json which is how V8 sets up the C++ JavaScript bindings. That is where all the magic happens :)
If you want to call privileged APIs from the content script you would need to add the namespace to chrome/renderer/resources/extensions/renderer_extension_bindings.js
For more information regarding the design of the Extension System in Chromium, there are good docs that explain the whole process model and interactions:
http://www.chromium.org/developers/design-documents/extensions

Make html validation part of build cycle

Currently when I build my site I have to manually open validate it at the 3wbc site (means when opera pops up, press ctr+alt+shft+u) for every page.
Is it possible to automatically validate every page whenever I build my pages?
P.s.: This page doesn't validate ;)
You can download and install your own copy of the validator - http://validator.w3.org/source/ - and invoke it locally instead of trekking out to w3.org for each page. Still, this requires piggybacking over a web server through plain HTTP or the API. For a simpler solution you may prefer to download the SP library - http://www.jclark.com/sp/index.htm or http://openjade.sourceforge.net/ - on which the W3 validator is based, then invoke the command 'nsgmls' from the command line.
There are of course also many desktop HTML validators that can process a batch of HTML pages at once, which may not be automated, but would certainly be much easier than manually checking each page. For example http://arealvalidator.com/ (Windows), http://www.webthing.com/software/validator-lite/install.html (Unix).
Might not be best choice for you but there's an Ant task for this: XmlValidate.
If you've got the HTML files in source control like SVN or Git, you can use a pre-commit hook script to run client-side validators on them. Or if you're feeling adventurous, you could use that method to ping another script on the server that validates the live pages...