Skip to content

Commit

Permalink
feat: building without node_modules
Browse files Browse the repository at this point in the history
  • Loading branch information
aleksandrjet committed Feb 28, 2024
1 parent f6fc18c commit b64a59d
Show file tree
Hide file tree
Showing 113 changed files with 2,559 additions and 110 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilly-owls-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@astrojs/node": minor
---

Added the `isIndependent: true` option to the `NodeJS` adapter. With this option, when building, the output folder `dist/server` will contain all the necessary data to start the server. And your server will be able to work without the `node_modules` folder
1 change: 1 addition & 0 deletions examples/independent-lit-build/.codesandbox/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FROM node:18-bullseye
21 changes: 21 additions & 0 deletions examples/independent-lit-build/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# build output
dist/
# generated types
.astro/

# dependencies
node_modules/

# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*


# environment variables
.env
.env.production

# macOS-specific files
.DS_Store
10 changes: 10 additions & 0 deletions examples/independent-lit-build/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM node:20-alpine3.18 AS base
WORKDIR /app

COPY package.json ./
COPY dist ./dist

ENV HOST=0.0.0.0
ENV PORT=4321
EXPOSE 4321
CMD node ./dist/server/entry.mjs
15 changes: 15 additions & 0 deletions examples/independent-lit-build/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { defineConfig } from 'astro/config';
import lit from '@astrojs/lit';
import node from '@astrojs/node';

// https://astro.build/config
export default defineConfig({
output: 'server',
integrations: [lit()],
adapter: node({
mode: 'standalone',
}),
experimental: {
isIndependent: true,
},
});
21 changes: 21 additions & 0 deletions examples/independent-lit-build/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@example/independent-lit-build",
"type": "module",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/lit": "^4.0.1",
"@astrojs/node": "^8.2.0",
"astro": "^4.4.3",
"date-fns": "^3.3.1",
"lit": "^3.1.2",
"nanoid": "^5.0.6"
}
}
3 changes: 3 additions & 0 deletions examples/independent-lit-build/src/components/Header.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.root {
text-align: center;
}
19 changes: 19 additions & 0 deletions examples/independent-lit-build/src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { LitElement, html } from 'lit';

import './Header.css';

export class Header extends LitElement {
static get properties() {
return {
text: {
type: String,
},
};
}

render() {
return html`<h1 class="root">${this.text}</h1>`;
}
}

customElements.define('my-header', Header);
17 changes: 17 additions & 0 deletions examples/independent-lit-build/src/components/Message.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { LitElement, html } from 'lit';

export class Message extends LitElement {
static get properties() {
return {
text: {
type: String,
},
};
}

render() {
return html`<p>${this.text}</p>`;
}
}

customElements.define('my-message', Message);
37 changes: 37 additions & 0 deletions examples/independent-lit-build/src/pages/index.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
import { format, parseISO } from 'date-fns';
import { Header } from '../components/Header';
const date = parseISO('2020-01-01T12:00:00');
const formattedDate = format(date, 'dd.MM.yy');
---

<html>
<head>
<title>Test app - Home</title>
<style>
body {
font-size: 18px;
}
</style>
</head>
<body>
{
(
// @ts-expect-error
<Header text="Home page" />
)
}
<p>
<a href="/product/123">/product</a>
</p>
<p>
<a href="/lazy-function">/lazy-function</a>
</p>
<p>
<a href="/lazy-component">/lazy-component</a>
</p>

<p>Some date: {formattedDate}</p>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
import { Header } from '../../components/Header';
import { Message } from '../../components/Message';
---

<html>
<head>
<title>Test app - Lazy component</title>
<style>
body {
font-size: 18px;
}
</style>
</head>
<body>
{
(
// @ts-expect-error
<Header text="Lazy component" />
)
}
<a href="/">home</a>
{
(
// @ts-expect-error
<Message text="my-message" />
)
}
</body>
</html>
30 changes: 30 additions & 0 deletions examples/independent-lit-build/src/pages/lazy-function/index.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
import { Header } from '../../components/Header';
const getRandomId = async () => {
const { nanoid } = await import('nanoid');
return nanoid();
};
---

<html>
<head>
<title>Test app - Lazy function</title>
<style>
body {
font-size: 18px;
}
</style>
</head>
<body>
{
(
// @ts-expect-error
<Header text="Lazy id generation" />
)
}

<a href="/">home</a>
<p>random id: {await getRandomId()}</p>
</body>
</html>
29 changes: 29 additions & 0 deletions examples/independent-lit-build/src/pages/product/[id].astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
import { formatISO } from 'date-fns';
import { Header } from '../../components/Header';
const val = Number(Astro.params.id);
const formattedDate = formatISO(new Date());
---

<html>
<head>
<title>Test app - Product</title>
<style>
body {
font-size: 18px;
}
</style>
</head>
<body>
{
(
// @ts-expect-error
<Header text={`Product ${val}`} />
)
}

<a href="/">home</a>
<p>Today: {formattedDate}</p>
</body>
</html>
7 changes: 7 additions & 0 deletions examples/independent-lit-build/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "astro/tsconfigs/base",
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react"
}
}
1 change: 1 addition & 0 deletions examples/independent-preact-build/.codesandbox/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FROM node:18-bullseye
21 changes: 21 additions & 0 deletions examples/independent-preact-build/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# build output
dist/
# generated types
.astro/

# dependencies
node_modules/

# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*


# environment variables
.env
.env.production

# macOS-specific files
.DS_Store
10 changes: 10 additions & 0 deletions examples/independent-preact-build/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM node:20-alpine3.18 AS base
WORKDIR /app

COPY package.json ./
COPY dist ./dist

ENV HOST=0.0.0.0
ENV PORT=4321
EXPOSE 4321
CMD node ./dist/server/entry.mjs
30 changes: 30 additions & 0 deletions examples/independent-preact-build/Dockerfile.withDeps
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
FROM node:20-alpine3.18 AS base
WORKDIR /app
COPY package.json ./

# Installing production dependencies
FROM base AS prod-deps
RUN npm install --production

# Installing development dependencies
FROM base AS build-deps
RUN npm install --production=false
RUN npm install sharp

# Building
FROM build-deps AS build
COPY src ./src
COPY public ./public
COPY astro.config.withDeps.mjs ./astro.config.mjs
COPY tsconfig.json .
RUN npm run build

# Running
FROM base AS runtime
COPY --from=prod-deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist

ENV HOST=0.0.0.0
ENV PORT=4321
EXPOSE 4321
CMD node ./dist/server/entry.mjs
73 changes: 73 additions & 0 deletions examples/independent-preact-build/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Astro + Preact Example

```sh
npm create astro@latest -- --template node-independent-build
```

[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/node-independent-build)
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/node-independent-build)
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/node-independent-build/devcontainer.json)

## Example of independent build

The `isIndependent: true` parameter has been added to the adapter config.

```js
// astro.config.mjs

export default defineConfig({
output: 'server',
adapter: node({
mode: 'standalone',
isIndependent: true,
}),
});
```

With this option, the output folder will contain everything needed to run your application. Once built, the application can run without the `node_modules` folder.

This option can be used to minimize the size of the Docker image.

### Docker images comparassion

- With the `isIndependent: false` parameter, the docker image with multi-step building occupies 244 MB, of which 109 MB is occupied by the `node_modules` folder
- With the `isIndependent: true` parameter, the docker image occupies 135 MB, of which 119 MB is occupied by the nodejs and ~500 kB by the app.

### Try to compare

```sh
# Build image with node_modules folder
docker build -t astro-with-deps -f Dockerfile.withDeps .

# Build image without dependencies
pnpm install
pnpm run build
docker build -t astro-without-deps -f Dockerfile .

# See image sizes
docker images | grep "^astro-"
# astro-without-deps 191e5842f27b 135MB
# astro-with-deps ad5d35dae534 244MB

# See more details
docker history astro-with-deps
# COPY /app/dist ./dist 243kB <-- your app
# ...
# COPY /app/node_modules ./node_modules 109MB <-- your node_modules
# /bin/sh -c addgroup node && addu… 119MB
# /bin/sh -c #(nop) ADD file:6dc… 7.66MB
# ...

docker history astro-without-deps
# COPY /app/dist ./dist # buildkit 541kB <-- your app
# ...
# /bin/sh -c addgroup node && addu… 119MB
# /bin/sh -c #(nop) ADD file:6dc… 7.66MB
# ...
```

### Explanation

Typically, when you build a production image, your image contains a `node_modules` folder with not only the necessary files, but also all the contents of your packages. These could be source-maps, README files, etc. that are not required in production.

With the new option, you can embed into your image only the necessary data that will definitely be used in production.
Loading

0 comments on commit b64a59d

Please sign in to comment.