ESBuild
We installed the esbuild-wasm
library since this represents the "Web assembly" version of ESBuild.
Web assembly is supported on all modern browsers, and can run compiled code from C
, C++
, Rust
, and Go
.
Transform
This is a limited special-case of build
that transforms a string of code
representing an in-memory file in an isolated environment that's completely disconnected from any other files.
Bundle
JS in the browser has no access to local filesystem.
So, we are intercepting the filesystem lookup step in the build process,
and redirecting it to the correct path on npmjs
(technically unpkg
).
As a bonus, unpkg
auto routes the requests to the latest version's index
file
(unless a specific version is mentioned in the request).
CSS workaround for in-browser loading
Add this to the plugin to load CSS into ESBuild
const cssContents = '...'; // Fetch from remote url
const escaped = cssContents
.replace(/\n/g, '')
.replace(/"/g, '\\"')
.replace(/'/g, "\\'");
const contents = `
const style = document.createElement('style');
style.innerText = '${escaped}';
document.head.appendChild(style);
`;
const result: esbuild.OnLoadResult = {
loader: 'jsx',
contents,
// ...
};
return result;
Note that this will not work if the CSS being loaded has imports of its own, or fetching font files using
url()
.
Plugins
Works similarly for Webpack too.
Trigger bundler | |
↓ | |
ESBuild bundling process | |
↓ | |
Locate index.js (or equivalent) |
onResolve step |
↓ | |
Load the index.js file |
onLoad step |
↓ | |
Parse index.js and find imports & exports |
|
↓ | |
Locate the imported file(s) |
onResolve step |
↓ | |
Load the imported file(s) |
onLoad step |
All work starts with the build
input, which represents the entire build process.
We primarily interact with it by attaching event listeners:
-
onResolve
: OverridesESbuild
's default process of locating a file, and returning a path. -
onLoad
: OverridesESbuild
's default step of fetching the file based on the path returned by the resolver.
We can define multiple onResolve
and multiple onLoad
functions within a single plugin,
for example, different listeners based on different values for the filter
parameter,
to choose different processing steps for different files.
namespace
can also be used to restrict the set of files processed by a given onResolve
or onLoad
.
If the callback functions exit without returning anything, then the bundler proceeds to the next callback in the queue. Otherwise, the bundler stops as soon as it finds a return value for that file.
URL function in Browser JS
-
new URL('./path', 'https://unpkg.com/package')
returnshttps://unpkg.com/path
inhref
. -
new URL('./path', 'https://unpkg.com/package/')
returnshttps://unpkg.com/package/path
inhref
. -
new URL('./', 'https://unpkg.com/package/src/index.js')
returns/package/src/
inpathname
, i.e., the directory containing the file.
Caching in browser
- Local storage - easy to use, but fairly limited in size.
- Indexed DB - harder to use, but larger.
HTML iframe
Used to embed one HTML document into another.
Use the sandbox
attribute to prevent malicious code in an iframe
from accessing the parent frame.
Two of those values, allow-scripts
and allow-same-origin
together can allow the embedded document to alter the sandbox attribute itself
if the iframe
and the parent have the same origin -
MDN docs.
So it's preferable to fetch the iframe
from a separate domain for maximal security.
allow-same-origin
This sandbox option allows the child and the parent windows to share contexts if they have the same:
- Domain
- Port
- Protocol (http vs https)
Disallowing this means that you also access to local storage and cookies.
We can still use
postMessage
and event listeners to indirectly communicate between two different contexts, and this is considered safe.
Open source browser-based editors
Code mirror | Ace Editor | Monaco Editor |
---|---|---|
Easiest to use | Moderately easy to use. Widely used | Hardest to setup |
Few out-of-the-box features | Additional features and languages supported via plugins | Almost perfect experience immediately |
React lib | React lib | React lib |
Webpack 5 polyfill fixes
Create React App v5 has many conflicts with some of these libraries even with the latest versions.
To avoid ejecting or downgrading to CRA v4, we can instead make use of the react-app-rewired
library.
Required steps:
-
Install dependencies
npm install --save-dev react-app-rewired constants-browserify \ assert os-browserify --legacy-peer-deps
npm install buffer process
-
In the root of your project (where the
package.json file
is located) create aconfig-overrides.js
file and add the following code:module.exports = function override(config, env) { config.resolve.fallback = { ...config.resolve.fallback, constants: require.resolve('constants-browserify'), assert: require.resolve('assert/'), os: require.resolve('os-browserify/browser'), }; config.ignoreWarnings = [ { message: /Critical dependency: the request of a dependency is an expression/, }, ]; return config; };
-
Update your
package.json
scripts to the following:{ "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-app-rewired eject" }
These changes will provide the missing native Node polyfills that Webpack v5 removed.
How to make a component take all remaining width
- Add
flex-grow: 1
to the component's CSS. - Add
width: 100%
to the component's children's CSS.
This tells the component to take up all remaining width without competing with its siblings.
Redux concepts
- Actions
- The different actions that we need to support on the state.
- For example, adding a new code cell, updating an existing cell, etc.
- They also define the payload that the action type needs for the state change.
- Basically one interface for each "action" we want to support.
- Action creators
- Functions that return an object that represents the corresponding action.
- For example, an object that contains all information about a new code cell that we are trying to create.
- The return type basically satisfies the interface corresponding to the "action".
- Instead of a return type, we can also
dispatch
the objects, where each object is a valid action. Useful when doing async work, like bundling code.
- Instead of a return type, we can also
- Reducers
- Functions that input the existing state and an action, and return the new state.
Easing functions for CSS
Fixing the add-bar
flicker issue
When we add a new cell, the add-bar
we had been hovering over fades out and immediately back in.
And the add-bar
right below fades out.
This happens because the add-bar
we had interacted with got pushed down, due to the insertion of a new cell.
There are two possible solutions to this.
1. Immediately hide the add-bar
upon click
-
:active
is triggered when the user interacts with the element. -
!important
is necessary to ensure that no other selector overrides it. -
The
add-bar
gets hidden even before the user has let go off the mouse click.
.add-cell:active {
opacity: 0 !important;
transition: opacity 0s;
}
2. Instead of inserting a cell before, add it after
This means that the new cell will be added below the add-bar
,
so the add-bar
being clicked won't change position in the DOM.