Skip to content

Latest commit

 

History

History
419 lines (320 loc) · 8.6 KB

f_features.md

File metadata and controls

419 lines (320 loc) · 8.6 KB
< E) Referencing files F) Features > G) Plugins

This page outlines the key features provided by Jsenv, including Node ESM resolution, magic extensions, import.meta.dev, injections, inlining, and handling UMD/CommonJS modules.

Table of contents

  1. Node ESM resolution
  2. Magic extensions
  3. import.meta.dev
  4. Injections
  5. Inlining
  6. Importing UMD
  7. Importing CommonJs
  8. Loading js module with

1. Node ESM resolution

Jsenv implements Node.js ESM resolution for JavaScript imports, enabling compatibility with Node.js module resolution in the browser.

Example:

import "amazing-package";

This is transformed into:

import "/node_modules/amazing-package/index.js";

See some of the features provided by Node ESM resolution:

1.1 Node module files during dev

During development, imports from node_modules are versioned using the package's version field from package.json.

Example:

import "/node_modules/amazing-package/index.js?v=1.0.0";

☝️ Here amazing-package/package.json contains "version": "1.0.0"

It allows to put node modules files into the browser cache up to 1 year.
A nice bonus: the version of the package is easily identifiable.

2. Magic extensions

Warning Magic extensions are becoming less common in the JavaScript ecosystem. Avoid using them when possible.

Jsenv supports magic extensions for resolving file imports without explicit extensions.

Example:

import "./file";

This is resolved to:

import "./file.js";

Disabling Magic Extensions:

import { startDevServer } from "@jsenv/core";

await startDevServer({
  sourceDirectoryUrl: new URL('../', import.meta.url),
+  magicExtensions: null,
+  magicDirectoryIndex: false,
});

import { build } from "@jsenv/core";

await build({
  sourceDirectoryUrl: new URL("../src/", import.meta.url),
  buildDirectoryUrl: new URL("../dist/", import.meta.url),
  entryPoints: { "./main.html": "main.html" },
+  magicExtensions: null,
+  magicDirectoryIndex: false,
});

3. import.meta.dev

Use import.meta.dev to differentiate behavior between development and production builds. Code specific to development is removed during the build process.

Example:

if (import.meta.dev) {
  console.log("during dev"); // logged when executing source files
} else {
  console.log("after build"); // logged when executing files after build
}

4. Injections

Jsenv allows injecting variables into file content during development and build.

Example:

// eslint-disable-next-line no-undef
window.__ENV__ = __ENV__;
console.log(window.__ENV__);

Development configuration:

import { startDevServer } from "@jsenv/core";

await startDevServer({
  sourceDirectoryUrl: new URL("../src/", import.meta.url),
  injections: {
    "./main.js": () => {
      return { __ENV__: "dev" };
    },
  },
});

The browser will see the following file content:

// eslint-disable-next-line no-undef
+ window.__ENV__ = "dev";
console.log(window.__ENV__);

Build configuration:

import { build } from "@jsenv/core";

await build({
  sourceDirectoryUrl: new URL("../src/", import.meta.url),
  buildDirectoryUrl: new URL("../dist/", import.meta.url),
  entryPoints: { "./main.html": "main.html" },
  injections: {
    "./main.js": () => {
      return { __ENV__: "build" };
    },
  },
});

The build file content:

// eslint-disable-next-line no-undef
+ window.__ENV__ = "build";
console.log(window.__ENV__);

It's possible to put injection logic into a single function:

// inside utils.mjs
export const getInjections = () => {
  return {
    "./main.js": (urlInfo) => {
      return {
        __ENV__: urlInfo.context.dev ? "dev" : "build",
      };
    },
  };
};

// inside dev.mjs
import { startDevServer } from "@jsenv/core";
import { getInjections } from "./utils.mjs";

await startDevServer({
  sourceDirectoryUrl: new URL("../src/", import.meta.url),
  injections: getInjections(),
});

// inside build.mjs
import { build } from "@jsenv/core";
import { getInjections } from "./utils.mjs";

await build({
  sourceDirectoryUrl: new URL("../src/", import.meta.url),
  buildDirectoryUrl: new URL("../dist/", import.meta.url),
  entryPoints: { "./main.html": "main.html" },
  injections: getInjections(),
});

And the function can be async:

export const getInjections = () => {
  return {
    "./main.js": async () => {
      const value = await Promise.resolve("toto");
      return { __ENV__: value };
    },
  };
};

5. Inlining

Inlining allows embedding code from separate files directly into HTML or other files.

Example:

<!doctype html>
<html>
  <head>
    <title>Title</title>
    <meta charset="utf-8" />
    <link rel="icon" href="data:," />
  </head>

  <body>
    <script src="./demo.js?inline"></script>
  </body>
</html>

This inlines demo.js into the HTML:

<script>
  console.log("Hello world");
</script>

6. Importing UMD

UMD (Universal Module Definition) modules can be imported directly. For packages like jquery:

Example:

import "jquery";

const jquery = window.$;

For packages like hls.js that require this to be defined, use the as_js_module query parameter:

Example:

import "hls.js?as_js_module";

window.Hls;

7. Importing CommonJs

CommonJS modules are not natively supported in browsers.

Example:

// main.js
module.exports = 42;

Error:

Uncaught ReferenceError: module is not defined

Solution: Use jsenvPluginCommonJs documented in G) Plugins#commonjs.

8. Loading js module with <script>

To load a JavaScript module with a classic <script> tag while retaining module features use jsenvPluginAsJsClassic.

Example:

<script src="file.js"></script>
// file.js
console.log(import.meta.url);

Error:

Uncaught SyntaxError: Cannot use import statement outside a module

Solution: Use jsenvPluginAsJsClassic documented in G) Plugins#asJsClassic.

< E) Referencing files F) Features > G) Plugins