├── .gitattributes
├── test
├── fixtures
│ ├── babel
│ │ ├── with-config
│ │ │ ├── .babelrc
│ │ │ └── index.js
│ │ └── object-rest-spread
│ │ │ └── index.js
│ ├── default.js
│ ├── banner
│ │ ├── index.js
│ │ └── package.json
│ ├── defaults
│ │ └── index.js
│ ├── uglify
│ │ └── index.js
│ ├── exclude-file
│ │ ├── foo.js
│ │ └── index.js
│ ├── extend-options
│ │ ├── bar.js
│ │ └── foo.js
│ ├── no-js-transform
│ │ └── index.js
│ ├── target
│ │ └── node
│ │ │ └── index.js
│ ├── buble
│ │ ├── react-jsx.js
│ │ ├── vue-jsx.js
│ │ ├── async.js
│ │ └── async-dot-dot-dot.js
│ ├── async
│ │ └── index.js
│ └── inline
│ │ └── index.js
├── demo
│ └── index.js
├── index.test.js
└── __snapshots__
│ └── index.test.js.snap
├── babel.js
├── examples
├── cli
│ ├── index.js
│ ├── cli.js
│ └── package.json
├── with-css
│ ├── index.js
│ ├── style.css
│ └── package.json
└── with-vue-component
│ ├── Component.vue
│ └── package.json
├── .gitignore
├── .vscode
└── settings.json
├── src
├── util.js
├── bili-error.js
├── emoji.js
├── template.js
├── progress-plugin.js
├── handle-error.js
├── virtual-modules-plugin.js
├── get-config.js
├── get-banner.js
├── cli.js
├── babel.js
├── logger.js
└── index.js
├── .bilirc
├── .editorconfig
├── docs
├── recipes
│ ├── inline-3rd-party-modules.md
│ ├── transpile-css-files.md
│ ├── transpile-vue-files.md
│ ├── update-package-json.md
│ ├── use-rollup-plugins.md
│ ├── transpile-js-files.md
│ └── bundle-multi-files.md
├── README.md
├── index.html
└── api.md
├── .babelrc
├── circle.yml
├── LICENSE
├── package.json
├── README.md
└── media
└── preview.svg
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/test/fixtures/babel/with-config/.babelrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/test/fixtures/default.js:
--------------------------------------------------------------------------------
1 | export default {}
2 |
--------------------------------------------------------------------------------
/babel.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./dist/babel')
2 |
--------------------------------------------------------------------------------
/examples/cli/index.js:
--------------------------------------------------------------------------------
1 | export default () => 42
2 |
--------------------------------------------------------------------------------
/examples/with-css/index.js:
--------------------------------------------------------------------------------
1 | import './style.css'
2 |
--------------------------------------------------------------------------------
/test/fixtures/banner/index.js:
--------------------------------------------------------------------------------
1 | export default 42
2 |
--------------------------------------------------------------------------------
/test/fixtures/defaults/index.js:
--------------------------------------------------------------------------------
1 | export default 42
2 |
--------------------------------------------------------------------------------
/test/fixtures/uglify/index.js:
--------------------------------------------------------------------------------
1 | export const a = 1
2 |
--------------------------------------------------------------------------------
/test/fixtures/exclude-file/foo.js:
--------------------------------------------------------------------------------
1 | export default 'foo'
2 |
--------------------------------------------------------------------------------
/examples/with-css/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: red;
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixtures/extend-options/bar.js:
--------------------------------------------------------------------------------
1 | export default 'bar'
2 |
--------------------------------------------------------------------------------
/test/fixtures/extend-options/foo.js:
--------------------------------------------------------------------------------
1 | export default 'foo'
2 |
--------------------------------------------------------------------------------
/test/fixtures/no-js-transform/index.js:
--------------------------------------------------------------------------------
1 | export default () => {}
--------------------------------------------------------------------------------
/test/fixtures/babel/with-config/index.js:
--------------------------------------------------------------------------------
1 | export default class {}
2 |
--------------------------------------------------------------------------------
/test/fixtures/target/node/index.js:
--------------------------------------------------------------------------------
1 | export default class Foo {}
2 |
--------------------------------------------------------------------------------
/test/fixtures/buble/react-jsx.js:
--------------------------------------------------------------------------------
1 | export default
hi
2 |
--------------------------------------------------------------------------------
/test/fixtures/babel/object-rest-spread/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | ...a
3 | }
--------------------------------------------------------------------------------
/test/fixtures/exclude-file/index.js:
--------------------------------------------------------------------------------
1 | import foo from './foo'
2 |
3 | export default foo
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | lib
4 | examples/*/yarn.lock
5 | examples/*/package-lock.json
6 | coverage/
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "prettier.eslintIntegration": true,
3 | "editor.formatOnSave": true
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixtures/async/index.js:
--------------------------------------------------------------------------------
1 | const a = { a: 1 }
2 |
3 | export default async () => {
4 | return { ...a }
5 | }
6 |
--------------------------------------------------------------------------------
/test/fixtures/buble/vue-jsx.js:
--------------------------------------------------------------------------------
1 | export default {
2 | render() {
3 | return hi
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/examples/cli/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import main from '.'
3 |
4 | const answer = main()
5 | console.log(answer)
6 |
--------------------------------------------------------------------------------
/test/fixtures/buble/async.js:
--------------------------------------------------------------------------------
1 | const a = async () => ({ a: 'a' })
2 |
3 | export default a().then(res => ({ ...res }))
4 |
--------------------------------------------------------------------------------
/test/fixtures/inline/index.js:
--------------------------------------------------------------------------------
1 | import all from 'p-all'
2 | import map from 'p-map'
3 |
4 | export default { all, map }
5 |
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 |
3 | export const relativePath = p => path.relative(process.cwd(), path.resolve(p))
4 |
--------------------------------------------------------------------------------
/test/fixtures/buble/async-dot-dot-dot.js:
--------------------------------------------------------------------------------
1 | export default async function () {
2 | return {
3 | ...(await this.bar())
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/test/demo/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import cac from 'cac'
3 |
4 | class Foo {
5 | async foo() {}
6 | }
7 |
8 | console.log(cac())
9 |
--------------------------------------------------------------------------------
/src/bili-error.js:
--------------------------------------------------------------------------------
1 | export default class BiliError extends Error {
2 | constructor(message) {
3 | super(message)
4 | this.name = 'BiliError'
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/cli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cli",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "build": "bili *.js"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.bilirc:
--------------------------------------------------------------------------------
1 | {
2 | "input": "src/{index,cli,babel}.js",
3 | "filename": "[name].js",
4 | "babel": {
5 | "babelrc": false
6 | },
7 | "target": "node",
8 | "external": ["./src/index.js"]
9 | }
10 |
--------------------------------------------------------------------------------
/examples/with-css/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-css",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "build": "bili index.js"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/test/fixtures/banner/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "foo",
3 | "version": "0.0.0",
4 | "year": 2015,
5 | "license": "MIT",
6 | "author": {
7 | "name": "name",
8 | "email": "email@email.com"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/docs/recipes/inline-3rd-party-modules.md:
--------------------------------------------------------------------------------
1 | # Inline 3rd-party modules
2 |
3 | By default Bili inlines all 3rd-party modules in `umd` and `iife` format into your bundle, however you can use [`inline`](/api#inline) option to toggle this.
4 |
5 | ```bash
6 | # Don't inline modules in UMD format
7 | bili --format umd --no-inline
8 | ```
9 |
--------------------------------------------------------------------------------
/examples/with-vue-component/Component.vue:
--------------------------------------------------------------------------------
1 |
2 | {{msg}}
3 |
4 |
5 |
14 |
15 |
20 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "targets": {
7 | "node": 6
8 | }
9 | }
10 | ]
11 | ],
12 | "plugins": [
13 | [
14 | "@babel/plugin-proposal-object-rest-spread",
15 | {
16 | "useBuiltIns": true
17 | }
18 | ]
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/src/emoji.js:
--------------------------------------------------------------------------------
1 | const supportsEmoji =
2 | process.platform !== 'win32' || process.env.TERM === 'xterm-256color'
3 |
4 | // Fallback symbols for Windows from https://en.wikipedia.org/wiki/Code_page_437
5 | export default {
6 | progress: supportsEmoji ? '⏳' : '∞',
7 | success: supportsEmoji ? '✨' : '√',
8 | error: supportsEmoji ? '🚨' : '×',
9 | warning: supportsEmoji ? '⚠️' : '‼'
10 | }
11 |
--------------------------------------------------------------------------------
/examples/with-vue-component/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-component",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "build": "bili Component.vue --plugin vue"
8 | },
9 | "devDependencies": {
10 | "rollup-plugin-vue": "3.0.0"
11 | },
12 | "dependencies": {
13 | "vue": "^2.5.13",
14 | "vue-template-compiler": "^2.5.13"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/docs/recipes/transpile-css-files.md:
--------------------------------------------------------------------------------
1 | # Transpile CSS files
2 |
3 | `Bili` supports CSS by default with the help from [rollup-plugin-postcss](https://github.com/egoist/rollup-plugin-postcss):
4 |
5 | The default options for `rollup-plugin-postcss`:
6 |
7 | ```js
8 | {
9 | extract: true
10 | }
11 | ```
12 |
13 | By default CSS files will be extracted to the same location where the JS is generated but with `.css` extension.
14 |
--------------------------------------------------------------------------------
/docs/recipes/transpile-vue-files.md:
--------------------------------------------------------------------------------
1 | # Transpile Vue files
2 |
3 | You can use [rollup-plugin-vue](https://github.com/vuejs/rollup-plugin-vue) to achieve this.
4 |
5 | ```bash
6 | cd my-project
7 | yarn add rollup-plugin-vue --dev
8 | bili --plugin vue
9 | # or with options
10 | bili --plugin vue --vue.css dist/style.css
11 | ```
12 |
13 | By default `~/dev/vue-final-formmaster❯❯bili--formatumd,umd-min--module-nameVueFinalForm--global.final-formfinal-form⏳Bundling1file:src/index.js⏳Bundling2files:src/Form.js⏳Bundling3files:node_modules/nano-assign/dist/nano-assign.common.js⏳Bundling4files:src/utils.js⏳Bundling5files:src/Field.js✨Builtin1.23s.┌──────────────────────────────────────────────┐│filesizegzipsize││dist/vue-final-form.js4.96KB1.66KB││dist/vue-final-form.min.js2.83KB1.06KB│└──────────────────────────────────────────────┘
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import util from 'util'
2 | import path from 'path'
3 | import EventEmitter from 'events'
4 | import globby from 'globby'
5 | import fs from 'fs-extra'
6 | import chalk from 'chalk'
7 | import { rollup, watch } from 'rollup'
8 | import camelcase from 'camelcase'
9 | import bytes from 'bytes'
10 | import gzipSize from 'gzip-size'
11 | import stringWidth from 'string-width'
12 | import boxen from 'boxen'
13 | import nodeResolvePlugin from 'rollup-plugin-node-resolve'
14 | import commonjsPlugin from 'rollup-plugin-commonjs'
15 | import jsonPlugin from 'rollup-plugin-json'
16 | import uglifyPlugin from 'rollup-plugin-uglify'
17 | import aliasPlugin from 'rollup-plugin-alias'
18 | import replacePlugin from 'rollup-plugin-replace'
19 | import hashbangPlugin from 'rollup-plugin-hashbang'
20 | import isBuiltinModule from 'is-builtin-module'
21 | import textTable from 'text-table'
22 | import resolveFrom from 'resolve-from'
23 | import isCI from 'is-ci'
24 | import virtualModulesPlugin from './virtual-modules-plugin'
25 | import progressPlugin from './progress-plugin'
26 | import template from './template'
27 | import getBanner from './get-banner'
28 | import { getBabelConfig, getBiliConfig } from './get-config'
29 | import BiliError from './bili-error'
30 | import { handleError, getDocRef } from './handle-error'
31 | import logger from './logger'
32 | import emoji from './emoji'
33 | import { relativePath } from './util'
34 |
35 | const FORMATS = ['cjs']
36 |
37 | const prettyBytes = v => bytes.format(v, { unitSeparator: ' ' })
38 |
39 | export default class Bili extends EventEmitter {
40 | static async generate(options) {
41 | const bundle = await new Bili(options).bundle({ write: false })
42 | return bundle
43 | }
44 |
45 | static async write(options) {
46 | const bundler = new Bili(options)
47 | const startTime = Date.now()
48 | try {
49 | await bundler.bundle()
50 | const buildTime = Date.now() - startTime
51 | const time =
52 | buildTime < 1000 ?
53 | `${buildTime}ms` :
54 | `${(buildTime / 1000).toFixed(2)}s`
55 |
56 | if (!options.watch) {
57 | logger.status(emoji.success, chalk.green(`Built in ${time}.`))
58 | logger.log(await bundler.stats())
59 | }
60 | return bundler
61 | } catch (err) {
62 | bundler.handleError(err)
63 | }
64 | }
65 |
66 | constructor(options = {}) {
67 | super()
68 | logger.setOptions(options)
69 | this.options = {
70 | outDir: 'dist',
71 | filename: '[name][suffix].js',
72 | cwd: process.cwd(),
73 | target: 'browser',
74 | js: 'babel',
75 | babel: {},
76 | ...(options.config !== false && getBiliConfig()),
77 | ...options
78 | }
79 | this.babelPresetOptions = {
80 | objectAssign: this.options.objectAssign,
81 | jsx: this.options.jsx,
82 | target: this.options.target,
83 | buble: false
84 | }
85 | this.options.babel = {
86 | ...getBabelConfig(
87 | this.options.cwd,
88 | this.options.babel.babelrc === false,
89 | this.babelPresetOptions
90 | ),
91 | ...this.options.babel
92 | }
93 | this.pkg = readPkg(this.options.cwd)
94 | this.pkgName = this.pkg.name && this.pkg.name.replace(/^@.+\//, '')
95 | this.bundles = {}
96 | this.cssBundles = new Map()
97 |
98 | this.handleError = err => handleError(err)
99 | }
100 |
101 | async stats() {
102 | const { bundles } = this
103 | const { sizeLimit } = this.options
104 | let leading = ''
105 | let sizeExceeded = false
106 | const sizes = await Promise.all(Object.keys(bundles)
107 | .sort()
108 | .map(async filepath => {
109 | const { code, relative, formatFull } = bundles[filepath]
110 | const gzipSizeNumber = await gzipSize(code)
111 | const expectedSize =
112 | sizeLimit &&
113 | sizeLimit[formatFull] &&
114 | bytes.parse(sizeLimit[formatFull])
115 | let sizeInfo
116 | if (expectedSize && gzipSizeNumber > expectedSize) {
117 | process.exitCode = 1
118 | sizeExceeded = true
119 | sizeInfo = chalk.red(` threshold: ${prettyBytes(expectedSize)}`)
120 | } else {
121 | sizeInfo = ''
122 | }
123 | return [
124 | relative,
125 | prettyBytes(code.length),
126 | chalk.green(prettyBytes(gzipSizeNumber)) + sizeInfo
127 | ]
128 | }))
129 |
130 | if (sizeExceeded) {
131 | leading = chalk.red(`${
132 | emoji.error
133 | } Bundle size exceeded the limit, check below for details.\n`)
134 | }
135 |
136 | await Promise.all(Array.from(this.cssBundles.keys())
137 | .sort()
138 | .map(async id => {
139 | const bundle = this.cssBundles.get(id)
140 | sizes.push([
141 | path.relative(process.cwd(), bundle.filepath),
142 | prettyBytes(bundle.code.length),
143 | chalk.green(prettyBytes(await gzipSize(bundle.code)))
144 | ])
145 | }))
146 |
147 | return (
148 | leading +
149 | boxen(textTable(
150 | [['file', 'size', 'gzip size'].map(v => chalk.bold(v)), ...sizes],
151 | {
152 | stringLength: stringWidth
153 | }
154 | ))
155 | )
156 | }
157 |
158 | resolveCwd(...args) {
159 | return path.resolve(this.options.cwd, ...args)
160 | }
161 |
162 | loadUserPlugins({ plugins, filename }) {
163 | // eslint-disable-next-line array-callback-return
164 | return plugins.map(pluginName => {
165 | // In bili.config.js or you're using the API
166 | // You can require rollup plugin directly
167 | if (typeof pluginName === 'object') {
168 | return pluginName
169 | }
170 |
171 | let pluginOptions = this.options[pluginName]
172 | if (pluginName === 'vue') {
173 | pluginOptions = {
174 | include: ['**/*.vue'],
175 | // Let rollup-plugin-postcss handle external CSS dependencies
176 | autoStyles: false,
177 | styleToImports: true,
178 | css: path.resolve(
179 | this.options.outDir,
180 | filename.replace(/\.[^.]+$/, '.css')
181 | ),
182 | ...pluginOptions
183 | }
184 | }
185 | const moduleName = isPath(pluginName) ?
186 | path.resolve(pluginName) :
187 | `rollup-plugin-${pluginName}`
188 | try {
189 | // TODO:
190 | // Local require is always relative to `process.cwd()`
191 | // Instead of `this.options.cwd`
192 | // We need to ensure that which is actually better
193 | return localRequire(moduleName)(pluginOptions)
194 | } catch (err) {
195 | handleLoadPluginError(moduleName, err)
196 | }
197 | })
198 | }
199 |
200 | async writeCSS() {
201 | await Promise.all(Array.from(this.cssBundles.keys()).map(id => {
202 | const { code, map, filepath } = this.cssBundles.get(id)
203 | return Promise.all([
204 | fs.writeFile(filepath, code, 'utf8'),
205 | map && fs.writeFile(`${filepath}.map`, map, 'utf8')
206 | ])
207 | }))
208 | }
209 |
210 | getJsOptions(name, pluginOptions) {
211 | if (name === 'babel') {
212 | return this.options.babel
213 | }
214 |
215 | if (name === 'typescript' || name === 'typescript2') {
216 | let typescript
217 | try {
218 | typescript = localRequire('typescript')
219 | } catch (err) {}
220 | return {
221 | typescript,
222 | ...pluginOptions
223 | }
224 | }
225 |
226 | if (name === 'buble') {
227 | return {
228 | ...pluginOptions,
229 | transforms: {
230 | // Skip transforming for..of
231 | forOf: false,
232 | ...(pluginOptions && pluginOptions.transforms)
233 | }
234 | }
235 | }
236 |
237 | return pluginOptions
238 | }
239 |
240 | // eslint-disable-next-line complexity
241 | async createConfig(
242 | { input, format, formatFull, compress },
243 | { multipleEntries }
244 | ) {
245 | const options = this.options.extendOptions ?
246 | this.options.extendOptions(this.options, {
247 | input,
248 | format,
249 | compress
250 | }) :
251 | this.options
252 |
253 | logger.debug(chalk.bold(`Bili options for ${input} in ${formatFull}:\n`) +
254 | util.inspect(options, { colors: true }))
255 |
256 | if (typeof options !== 'object') {
257 | throw new BiliError('You must return the options in `extendOptions` method!')
258 | }
259 |
260 | const {
261 | outDir,
262 | filename,
263 | inline = format === 'umd' || format === 'iife'
264 | } = options
265 |
266 | const sourceMap = typeof options.map === 'boolean' ? options.map : compress
267 |
268 | const outFilename = getFilename({
269 | input,
270 | format,
271 | filename,
272 | compress,
273 | // If it's not bundling multi-entry
274 | // The name can fallback to pkg name
275 | name: options.name || (!multipleEntries && this.pkgName)
276 | })
277 | // The path to output file
278 | // Relative to `this.options.cwd`
279 | const file = this.resolveCwd(outDir, outFilename)
280 |
281 | const transformJS = options.js !== false
282 | const jsPluginName = transformJS && getJsPluginName(options.js, input)
283 | const jsPlugin = transformJS && getJsPlugin(jsPluginName)
284 | const jsOptions =
285 | transformJS && this.getJsOptions(jsPluginName, options[jsPluginName])
286 |
287 | const banner = getBanner(options.banner, this.pkg)
288 |
289 | let external = getArrayOption(options, 'external') || []
290 | external = external.map(e => (e.startsWith('./') ? path.resolve(options.cwd, e) : e))
291 | let globals = options.globals || options.global
292 | if (typeof globals === 'object') {
293 | external = [...new Set(external.concat(Object.keys(globals)))]
294 | }
295 |
296 | let env = options.env
297 | if (format === 'umd' || format === 'iife') {
298 | env = {
299 | NODE_ENV: compress ? 'production' : 'development',
300 | ...env
301 | }
302 | }
303 |
304 | const inputOptions = {
305 | input,
306 | external,
307 | onwarn: err => {
308 | if (options.quiet) return
309 |
310 | if (typeof err === 'string') {
311 | return logger.warn(err)
312 | }
313 |
314 | const { loc, frame, message, code, source } = err
315 |
316 | if (options.quiet || code === 'THIS_IS_UNDEFINED') {
317 | return
318 | }
319 | // Unresolved modules
320 | // If `inline` is not trusty there will always be this warning
321 | // But we only need this when the module is not installed
322 | // i.e. does not exist on disk
323 | if (code === 'UNRESOLVED_IMPORT' && source) {
324 | if (
325 | // Skip sub path for now
326 | source.indexOf('/') === -1 &&
327 | // Skip built-in modules
328 | !isBuiltinModule(source) &&
329 | // Check if the module exists
330 | resolveFrom.silent(process.cwd(), source) === null
331 | ) {
332 | logger.warn(`Module "${source}" was not installed, you may run "${chalk.cyan(`${getPackageManager()} add ${source}`)}" to install it!`)
333 | }
334 | return
335 | }
336 | // print location if applicable
337 | if (loc) {
338 | logger.warn(`${loc.file} (${loc.line}:${loc.column}) ${message}`)
339 | if (frame) logger.warn(chalk.dim(frame))
340 | } else {
341 | logger.warn(message)
342 | }
343 | },
344 | plugins: [
345 | !isCI &&
346 | process.stderr.isTTY &&
347 | process.env.NODE_ENV !== 'test' &&
348 | options.progress !== false &&
349 | progressPlugin(),
350 | hashbangPlugin(),
351 | options.virtualModules &&
352 | virtualModulesPlugin(options.virtualModules, this.options.cwd),
353 | ...this.loadUserPlugins({
354 | filename: outFilename,
355 | plugins: getArrayOption(options, 'plugin') || []
356 | }),
357 | jsonPlugin(),
358 | require('rollup-plugin-postcss')({
359 | extract: true,
360 | minimize: compress,
361 | sourceMap,
362 | ...options.postcss,
363 | onExtract: getExtracted => {
364 | // Use `z` `a` to ensure the order when we log the stats
365 | const id = `${input}::${compress ? 'z-compressed' : 'a-normal'}`
366 | if (!this.cssBundles.has(id)) {
367 | // Don't really need suffix for format
368 | const filepath = this.resolveCwd(
369 | outDir,
370 | outFilename.replace(
371 | /(\.(iife|cjs|es))?(\.min)?\.js$/,
372 | compress ? '.min.css' : '.css'
373 | )
374 | )
375 | const bundle = getExtracted(filepath)
376 |
377 | this.cssBundles.set(id, {
378 | ...bundle,
379 | filepath
380 | })
381 | }
382 | // We extract CSS but never atually let `rollup-plugin-postcss` write to disk
383 | // To prevent from duplicated css files
384 | return false
385 | }
386 | }),
387 | transformJS &&
388 | jsPluginName === 'buble' &&
389 | require('rollup-plugin-babel')({
390 | include: '**/*.js',
391 | exclude: 'node_modules/**',
392 | babelrc: false,
393 | presets: [
394 | [
395 | require.resolve('./babel'),
396 | {
397 | ...this.babelPresetOptions,
398 | buble: true
399 | }
400 | ]
401 | ]
402 | }),
403 | transformJS &&
404 | jsPlugin({
405 | exclude: 'node_modules/**',
406 | ...jsOptions
407 | }),
408 | inline &&
409 | nodeResolvePlugin({
410 | module: true,
411 | extensions: ['.js', '.json'],
412 | preferBuiltIns: true,
413 | browser: !options.target.startsWith('node'),
414 | ...options.nodeResolve
415 | }),
416 | commonjsPlugin(options.commonjs),
417 | compress &&
418 | uglifyPlugin({
419 | ...options.uglify,
420 | output: {
421 | ...(options.uglify && options.uglify.output),
422 | // Add banner (if there is)
423 | preamble: banner
424 | }
425 | }),
426 | options.alias && aliasPlugin(options.alias),
427 | options.replace && replacePlugin(options.replace),
428 | {
429 | name: 'bili',
430 | ongenerate: (_, { code }) => {
431 | this.bundles[file] = {
432 | relative: path.relative(process.cwd(), file),
433 | input,
434 | format,
435 | formatFull,
436 | compress,
437 | code
438 | }
439 | }
440 | },
441 | env &&
442 | Object.keys(env).length > 0 &&
443 | replacePlugin({
444 | values: Object.keys(env).reduce((res, key) => {
445 | res[`process.env.${key}`] = JSON.stringify(env[key])
446 | return res
447 | }, {})
448 | })
449 | ].filter(v => Boolean(v))
450 | }
451 |
452 | const outputOptions = {
453 | format,
454 | globals,
455 | name: this.getModuleName(format),
456 | file,
457 | banner,
458 | exports: options.exports,
459 | sourcemap: sourceMap
460 | }
461 |
462 | return {
463 | inputOptions,
464 | outputOptions
465 | }
466 | }
467 |
468 | async bundle({ write = true } = {}) {
469 | let inputFiles = this.options.input
470 | if (!inputFiles || inputFiles.length === 0) {
471 | inputFiles = ['src/index.js']
472 | }
473 | if (!Array.isArray(inputFiles)) {
474 | inputFiles = [inputFiles]
475 | }
476 |
477 | const magicPatterns = []
478 | inputFiles = inputFiles.filter(v => {
479 | if (globby.hasMagic(v)) {
480 | magicPatterns.push(v)
481 | return false
482 | }
483 | return true
484 | })
485 |
486 | await globby(magicPatterns, { cwd: this.options.cwd }).then(files => {
487 | inputFiles = inputFiles
488 | .concat(files)
489 | .map(v => relativePath(this.resolveCwd(v)))
490 | })
491 |
492 | if (inputFiles.length === 0) {
493 | throw new BiliError('No matched files to bundle.')
494 | }
495 |
496 | const formats = getArrayOption(this.options, 'format') || FORMATS
497 |
498 | const options = inputFiles.reduce(
499 | (res, input) => [
500 | ...res,
501 | ...formats.map(format => {
502 | const compress = format.endsWith('-min')
503 | return {
504 | input,
505 | format: format.replace(/-min$/, ''),
506 | formatFull: format,
507 | compress
508 | }
509 | })
510 | ],
511 | []
512 | )
513 |
514 | const multipleEntries = inputFiles.length > 1
515 | const actions = options.map(async option => {
516 | const { inputOptions, outputOptions } = await this.createConfig(option, {
517 | multipleEntries
518 | })
519 |
520 | logger.debug(chalk.bold(`Rollup input options for bundling ${option.input} in ${
521 | option.formatFull
522 | }:\n`) + util.inspect(inputOptions, { colors: true }))
523 |
524 | logger.debug(chalk.bold(`Rollup output options for bundling ${option.input} in ${
525 | option.formatFull
526 | }:\n`) + util.inspect(outputOptions, { colors: true }))
527 |
528 | if (this.options.watch) {
529 | const watcher = watch({
530 | ...inputOptions,
531 | output: outputOptions,
532 | watch: {
533 | clearScreen: true
534 | }
535 | })
536 | watcher.on('event', async e => {
537 | if (e.code === 'ERROR' || e.code === 'FATAL') {
538 | handleError(e.error)
539 | }
540 | if (e.code === 'BUNDLE_END') {
541 | process.exitCode = 0
542 | logger.write(await this.stats())
543 | }
544 | })
545 | return
546 | }
547 |
548 | const bundle = await rollup(inputOptions)
549 | if (write) return bundle.write(outputOptions)
550 | return bundle.generate(outputOptions)
551 | })
552 |
553 | await Promise.all(actions)
554 |
555 | // Since we update `this.bundles` in Rollup plugin's `ongenerate` callback
556 | // We have to put follow code into another callback to execute at th end of call stack
557 | await nextTick()
558 | const bundleCount = Object.keys(this.bundles).length
559 |
560 | if (bundleCount < formats.length * inputFiles.length) {
561 | const hasName = this.options.filename.includes('[name]')
562 | const hasSuffix = this.options.filename.includes('[suffix]')
563 | const msg = `Multiple files are emitting to the same path.\nPlease check if ${
564 | hasName || inputFiles.length === 1 ?
565 | '' :
566 | `${chalk.green('[name]')}${hasSuffix ? '' : ' or '}`
567 | }${hasSuffix ? '' : chalk.green('[suffix]')} is missing in ${chalk.green('filename')} option.\n${getDocRef('api', 'filename')}`
568 |
569 | throw new BiliError(msg)
570 | }
571 |
572 | // Write potential CSS files
573 | await this.writeCSS()
574 |
575 | return this
576 | }
577 |
578 | getModuleName(format) {
579 | if (format !== 'umd' && format !== 'iife') return undefined
580 | return (
581 | this.options.moduleName ||
582 | this.pkg.moduleName ||
583 | (this.pkgName ? camelcase(this.pkgName) : undefined)
584 | )
585 | }
586 | }
587 |
588 | function getSuffix(format) {
589 | let suffix = ''
590 | switch (format) {
591 | case 'cjs':
592 | suffix += '.cjs'
593 | break
594 | case 'umd':
595 | break
596 | case 'es':
597 | suffix += '.es'
598 | break
599 | case 'iife':
600 | suffix += '.iife'
601 | break
602 | default:
603 | throw new Error('unsupported format')
604 | }
605 | return suffix
606 | }
607 |
608 | function getNameFromInput(input) {
609 | return path.basename(input, path.extname(input))
610 | }
611 |
612 | function getFilename({ input, format, filename, compress, name }) {
613 | name = name || getNameFromInput(input)
614 | const suffix = getSuffix(format)
615 | const res = template(filename, { name, suffix })
616 | return compress ?
617 | path.basename(res, path.extname(res)) + '.min' + path.extname(res) :
618 | res
619 | }
620 |
621 | function getJsPlugin(name) {
622 | let req
623 | if (isPath(name)) {
624 | req = require
625 | name = path.resolve(name)
626 | } else {
627 | req = name === 'babel' || name === 'buble' ? require : localRequire
628 | name = `rollup-plugin-${name}`
629 | }
630 | try {
631 | return req(name)
632 | } catch (err) {
633 | handleLoadPluginError(name, err)
634 | }
635 | }
636 |
637 | function localRequire(name) {
638 | return require(path.resolve('node_modules', name))
639 | }
640 |
641 | function isPath(v) {
642 | return /^[./]|(^[a-zA-Z]:)/.test(v)
643 | }
644 |
645 | function handleLoadPluginError(moduleName, err) {
646 | if (err.code === 'MODULE_NOT_FOUND' && err.message.includes(moduleName)) {
647 | let msg = `Cannot find plugin "${chalk.cyan(moduleName)}" in current directory!`
648 | const pm = getPackageManager()
649 | const command =
650 | pm === 'yarn' ?
651 | `yarn add ${moduleName} --dev` :
652 | `npm install -D ${moduleName}`
653 | if (!isPath(moduleName)) {
654 | msg += `\n${chalk.dim(`You may run "${command}" to install it.`)}`
655 | }
656 | throw new BiliError(msg)
657 | } else {
658 | throw err
659 | }
660 | }
661 |
662 | function nextTick() {
663 | return new Promise(resolve => {
664 | process.nextTick(() => {
665 | resolve()
666 | })
667 | })
668 | }
669 |
670 | function getArrayOption(options, name) {
671 | const option = options[name] || options[`${name}s`]
672 | if (typeof option === 'string') return option.split(',')
673 | return option
674 | }
675 |
676 | let packageManager
677 |
678 | function getPackageManager() {
679 | if (packageManager) return packageManager
680 | packageManager = fs.existsSync('yarn.lock') ? 'yarn' : 'npm'
681 | return packageManager
682 | }
683 |
684 | function readPkg(cwd = process.cwd()) {
685 | try {
686 | return require(path.resolve(cwd, 'package.json'))
687 | } catch (err) {
688 | if (err.code === 'MODULE_NOT_FOUND') {
689 | return {}
690 | }
691 | throw err
692 | }
693 | }
694 |
695 | function getJsPluginName(name, input) {
696 | if (name) {
697 | return name
698 | }
699 |
700 | if (input.endsWith('.ts')) {
701 | return 'typescript2'
702 | }
703 |
704 | return 'babel'
705 | }
706 |
--------------------------------------------------------------------------------