packen
TypeScript icon, indicating that this package has built-in type declarations

0.3.3 • Public • Published

types version GitHub Workflow Status


packen collects isomorphic JavaScript code when it runs on the server, and prepares it for shipping to and running on the client. Useful for server side rendering (SSR), static site generation (SSG), etc.

// isomorphic code:
import { packMe } from 'packen'

packMe()

// ...
node -r packen/r server-code.js


Installation

You need Node.js for using packen.

npm i packen

Usage

To use packen, you need to:

  • Mark your isomorphic code
  • Run your server code, which may use (some of) your isomorphic code

packen will keep track of what was used and bundle it for you (or help you bundle it using your own toolchain).


👉 Use packMe() to mark your isomorphic code:

import { packMe } from 'packen'

packMe()
// this file will be included in the bundle

You can also conditionally mark a file:

if (condition()) {
  packMe()
}

// this file will be included in the bundle
// only if condition() is true.
export function func() {
  packMe()
  // this file will be included in the bundle
  // only when func() is called.
  
  // ...
}

You can create the bundle either programmatically or using the CLI. packen is mainly designed to be used programmatically (as part of other tooling), but the CLI route offers a convenient out-of-the-box method suitable for static site generation (SSG), albeit with limited configuration options.


CLI

node -r packen/r sever_code.js
ts-node -r packen/r server_code.ts

☝️ This will execute server_code.js, and bundle any marked isomorphic code in an output bundle.js. You can customize the output file by providing the PACKEN_TO environment variable:

PACKEN_TO=dist/chunk.js node -r packen/r server_code.js
PACKEN_TO=dist/chunk.js ts-node -r packen/r server_code.ts

Programmatic API

import { Bundle, build } from 'packen/server'

//
// 👉 STEP 1: create a bundle
//
const bundle = new Bundle()

//
// 👉 STEP 2: run your isomorphic code
//
import './my/iso.js'

...

//
// 👉 STEP 3: build the bundle
//
await build(bundle, 'dist/chunk.js')

A Bundle MUST have been created before the isomorphic code is executed. Calling packMe() when no bundle is created will have no effect. Additionally, when you build a bundle, it is closed, which means it can no longer collect isomorphic code, and you need to make a new bundle.


packen provides various methods for building bundles:

  • build(): creates a bundle from collected code using esbuild and writes it to given file.
  • pack(): creates a bundle from collected code using esbuild and returns it as a string.
  • write(): creates an entry point from collected code and writes it to given file. You might need to bundle this entry file using your own bundler before shipping it to client (tools such as Vite can consume it directly).
  • serialize(): creates an entry point from collected code and returns it as a string.

Build

build(bundle: Bundle, path: string, processor?: Processor): void

Builds given bundle, bundles and minifies it (using esbuild) and writes it to given path. If a processor is provided, will be used for processing earmarked entries (see Extension).

import { Bundle, build } from 'packen/server'

const bundle = new Bundle()

// ...

build(bundle, 'dist/bundle.js')

Pack

pack(bundle: Bundle, processor?: Processor): string

Builds given bundle, returning the bundled and minified code as a strin (uses esbuild). If a processor is provided, will be used for processing earmarked entries (see Extension).

import { Bundle, pack } from 'packen/server'

const bundle = new Bundle()

// ...

const code = pack(bundle)

This method can be used for generating server-side HTML:

const myHTML = html`
  <html>
    <head>
      <script type="module">
        ${pack(bundle)}
      </script>
    </head>
    <body>
      <!-- ... -->
    </body>
  </html>
`

Write

write(bundle: Bundle, path: string, processor?: Processor): void

Builds an entry file for given bundle, and writes it to given path. If a processor is provided, will be used for processing earmarked entries (see Extension). DOES NOT bundle or minify the code.

import { Bundle, write } from 'packen/server'

const bundle = new Bundle()

// ...

write(bundle, 'dist/entry.js')

This entry file can be used with other bundlers, like Vite:

<!-- index.html -->
<script type="module" src="dist/entry.js"></script>
<!-- ... -->
vite build

Serialize

serialize(bundle: Bundle, processor?: Processor): string

Builds an entry file for given bundle, returning the code as a string. If a processor is provided, will be used for processing earmarked entries (see Extension). DOES NOT bundle or minify the code.

import { Bundle, serialize } from 'packen/server'

const bundle = new Bundle()

// ...

const code = serialize(bundle)

This is particularly useful when you want to use other bundlers programmatically (or even using esbuild with some custom configuration):

import { build } from 'esbuild'

  await build({
    stdin: {
      contents: serialize(bundle, processor),
      resolveDir: process.cwd(),
    },
    // your esbuild configuration
  })

Advanced Usage

👉 Use packCaller() from inside a function to pack the code that called that function:

import { packCaller } from 'packen'

// any code calling func will now be packed automatically.
export function func() {
  packCaller()

  // ...
}

👉 Use mark() and collect() to create a mark that can be called later (perhaps due to some later event):

import { mark, collect } from 'packen'


const flag = mark()

// when laterCallback is called, this file will be collected and bundled.
export function laterCallback() {
  collect(flag)

  // ...
}

👉 Use markCaller() and collect() to create a mark for the calling code that can be called later (perhaps due to some later event):

import { markCaller, collect } from 'packen'


function someFunc() {
  const flag = markCaller()

  // when this is called, the code that called someFunc will be collected and bundled.
  return () => collect(flag)
}

Extension

By default, packen will use bare imports, collecting files as side-effect:

// entry file

import '/Path/to/some/isomorphic.js'

// ...

You can change this behavior by providing a processor function to bundling functions. This processor will be passed a CallSite, and should turn it into some valid JavaScript string. For example, the following custom processor allows collecting specific functions, which then will be executed on client side:

export dryRun = entry => {
  const func = entry.getFunctionName()
  const file = entry.getFileName()
  
  return `
    import { ${func} } from '${file}';
    ${func}();
  `
}

This processor can be used like this:

// ...

build(bundle, 'dist/bundle.js', dryRun)
// isomorphic code

import { packMe } from 'packen'

export const myFunc = () => {

  // this function will be collected for client side bundling
  // and executed on client bootstrap.
  packMe()
  
  // do other stuff
}

Contribution

You need node, NPM to start and git to start.

# clone the code
git clone git@github.com:loreanvictor/packen.git
# install stuff
npm i

Make sure all checks are successful on your PRs. This includes all tests passing, high code coverage, correct typings and abiding all the linting rules. The code is typed with TypeScript, Jest is used for testing and coverage reports, ESLint and TypeScript ESLint are used for linting. Subsequently, IDE integrations for TypeScript and ESLint would make your life much easier (for example, VSCode supports TypeScript out of the box and has this nice ESLint plugin), but you could also use the following commands:

# run tests
npm test
# check code coverage
npm run coverage
# run linter
npm run lint



Readme

Keywords

none

Package Sidebar

Install

npm i packen

Weekly Downloads

1

Version

0.3.3

License

MIT

Unpacked Size

31.5 kB

Total Files

51

Last publish

Collaborators

  • lorean.victor