1.0.0 • Published 6 months ago

@ashenote/local-client v1.0.0

Weekly downloads
-
License
-
Repository
-
Last release
6 months ago

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 fileonLoad 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: Overrides ESbuild's default process of locating a file, and returning a path.
  • onLoad: Overrides ESbuild'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') returns https://unpkg.com/path in href.
  • new URL('./path', 'https://unpkg.com/package/') returns https://unpkg.com/package/path in href.
  • new URL('./', 'https://unpkg.com/package/src/index.js') returns /package/src/ in pathname, i.e., the directory containing the file.

Caching in browser

  1. Local storage - easy to use, but fairly limited in size.
  2. 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: 1. Domain 2. Port 3. 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 mirrorAce EditorMonaco Editor
Easiest to useModerately easy to use. Widely usedHardest to setup
Few out-of-the-box featuresAdditional features and languages supported via pluginsAlmost perfect experience immediately
React libReact libReact 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:

  1. Install dependencies

    npm install --save-dev react-app-rewired constants-browserify \
      assert os-browserify --legacy-peer-deps
    npm install buffer process
  2. In the root of your project (where the package.json file is located) create a config-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;
    };
  3. 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

  1. Add flex-grow: 1 to the component's CSS.
  2. 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

  1. 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.
  2. 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.
  3. Reducers
    • Functions that input the existing state and an action, and return the new state.

Easing functions for CSS

https://easings.net

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.