summaryrefslogtreecommitdiff
path: root/rollup.config.js
blob: cc583c59c799144a32019042b937809c934e901f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
// @ts-check
import * as fs from "fs";
import path from "path";
import alias from "rollup-plugin-alias";
import { plugin as analyze } from "rollup-plugin-analyzer";
import commonjs from "rollup-plugin-commonjs";
import globals from "rollup-plugin-node-globals";
import nodeResolve from "rollup-plugin-node-resolve";
import typescriptPlugin from "rollup-plugin-typescript2";
import { createFilter } from "rollup-pluginutils";
import replace from "rollup-plugin-replace";
import typescript from "typescript";

const mockPath = path.resolve(__dirname, "js/mock_builtin.js");
const tsconfig = path.resolve(__dirname, "tsconfig.json");
const typescriptPath = path.resolve(
  __dirname,
  "third_party/node_modules/typescript/lib/typescript.js"
);

// We will allow generated modules to be resolvable by TypeScript based on
// the current build path
const tsconfigOverride = {
  compilerOptions: {
    paths: {
      "*": ["*", path.resolve("*")]
    }
  }
};

const archNodeToDeno = {
  x64: "x64"
};
const osNodeToDeno = {
  win32: "win",
  darwin: "mac",
  linux: "linux"
};

function generateDepFile({ outputFile, sourceFiles = [], configFiles = [] }) {
  let timestamp = new Date();

  // Save the depfile just before the node process exits.
  process.once("beforeExit", () =>
    writeDepFile({ outputFile, sourceFiles, configFiles, timestamp })
  );

  return {
    name: "depfile",
    load(sourceFile) {
      // The 'globals' plugin adds generated files that don't exist on disk.
      // Don't add them to the depfile.
      if (/^[0-9a-f]{30}$/.test(sourceFile)) {
        return;
      }
      sourceFiles.push(sourceFile);
      // Remember the time stamp that we last resolved a dependency.
      // We'll set the last modified time of the depfile to that.
      timestamp = new Date();
    }
  };
}

function writeDepFile({ outputFile, sourceFiles, configFiles, timestamp }) {
  const buildDir = process.cwd();
  const outputDir = path.dirname(outputFile);

  // Assert that the discovered bundle inputs are files that exist on disk.
  sourceFiles.forEach(f => fs.accessSync(f));
  // Since we also want to rebuild the bundle if rollup configuration or the the
  // tooling changes (e.g. when typescript is updated), add the currently loaded
  // node.js modules to the list of dependencies.
  let inputs = [...sourceFiles, ...configFiles, ...Object.keys(require.cache)];
  // Deduplicate the list of inputs.
  inputs = Array.from(new Set(inputs.map(f => path.resolve(f))));
  // Turn filenames into relative paths and format/escape them for a Makefile.
  inputs = inputs.map(formatPath);

  // Build a list of output filenames and normalize those too.
  const depFile = path.join(
    outputDir,
    path.basename(outputFile, path.extname(outputFile)) + ".d"
  );
  const outputs = [outputFile, depFile].map(formatPath);

  // Generate depfile contents.
  const depFileContent = [
    ...outputs.map(filename => `${filename}: ` + inputs.join(" ") + "\n\n"),
    ...inputs.map(filename => `${filename}:\n`)
  ].join("");

  // Since we're writing the depfile when node's "beforeExit" hook triggers,
  // it's getting written _after_ the regular outputs are saved to disk.
  // Therefore, after writing the depfile, reset its timestamps to when we last
  // discovered a dependency, which was certainly before the bundle was built.
  fs.writeFileSync(depFile, depFileContent);
  fs.utimesSync(depFile, timestamp, timestamp);

  // Renders path to make it suitable for a depfile.
  function formatPath(filename) {
    // Make the path relative to the root build directory.
    filename = path.relative(buildDir, filename);
    // Use forward slashes on Windows.
    if (process.platform === "win32") {
      filename = filename.replace(/\\/g, "/");
    }
    // Escape spaces with a backslash. This is what rust and clang do too.
    filename = filename.replace(/ /g, "\\ ");
    return filename;
  }
}

export default function makeConfig(commandOptions) {
  return {
    output: {
      format: "iife",
      name: "denoMain",
      sourcemap: true,
      sourcemapExcludeSources: true
    },

    plugins: [
      // inject build and version info
      replace({
        ROLLUP_REPLACE_TS_VERSION: typescript.version,
        ROLLUP_REPLACE_ARCH: archNodeToDeno[process.arch],
        ROLLUP_REPLACE_OS: osNodeToDeno[process.platform]
      }),

      // would prefer to use `rollup-plugin-virtual` to inject the empty module, but there
      // is an issue with `rollup-plugin-commonjs` which causes errors when using the
      // virtual plugin (see: rollup/rollup-plugin-commonjs#315), this means we have to use
      // a physical module to substitute
      alias({
        fs: mockPath,
        path: mockPath,
        os: mockPath,
        crypto: mockPath,
        buffer: mockPath,
        module: mockPath
      }),

      // Allows rollup to resolve modules based on Node.js resolution
      nodeResolve(),

      // Allows rollup to import CommonJS modules
      commonjs({
        namedExports: {
          // Static analysis of `typescript.js` does detect the exports properly, therefore
          // rollup requires them to be explicitly defined to make them available in the
          // bundle
          [typescriptPath]: [
            "convertCompilerOptionsFromJson",
            "createLanguageService",
            "createProgram",
            "createSourceFile",
            "getPreEmitDiagnostics",
            "formatDiagnostics",
            "formatDiagnosticsWithColorAndContext",
            "parseConfigFileTextToJson",
            "version",
            "CompilerHost",
            "DiagnosticCategory",
            "Extension",
            "ModuleKind",
            "ScriptKind",
            "ScriptSnapshot",
            "ScriptTarget"
          ]
        }
      }),

      typescriptPlugin({
        // The build script is invoked from `out/:target` so passing an absolute file path is needed
        tsconfig,

        // This provides any overrides to the `tsconfig.json` that are needed to bundle
        tsconfigOverride,

        // This provides the locally configured version of TypeScript instead of the plugins
        // default version
        typescript,

        // By default, the include path only includes the cwd and below, need to include the root of the project
        // and build path to be passed to this plugin.  This is different front tsconfig.json include
        include: ["*.ts", `${__dirname}/**/*.ts`, `${process.cwd()}/**/*.ts`],

        // d.ts files are not bundled and by default like include, it only includes the cwd and below
        exclude: [
          "*.d.ts",
          `${__dirname}/**/*.d.ts`,
          `${process.cwd()}/**/*.d.ts`
        ]
      }),

      // Provide some concise information about the bundle
      analyze({
        skipFormatted: true,
        onAnalysis({
          bundleSize,
          bundleOrigSize,
          bundleReduction,
          moduleCount
        }) {
          if (!commandOptions.silent) {
            console.log(
              `Bundle size: ${Math.round((bundleSize / 1000000) * 100) / 100}Mb`
            );
            console.log(
              `Original size: ${Math.round((bundleOrigSize / 1000000) * 100) /
                100}Mb`
            );
            console.log(`Reduction: ${bundleReduction}%`);
            console.log(`Module count: ${moduleCount}`);
          }
        }
      }),

      // source-map-support, which is required by TypeScript to support source maps, requires Node.js Buffer
      // implementation.  This needs to come at the end of the plugins because of the impact it has on
      // the existing runtime environment, which breaks other plugins and features of the bundler.
      globals(),

      generateDepFile({
        outputFile: commandOptions.o,
        configFiles: [commandOptions.c, tsconfig]
      })
    ]
  };
}