├── test
├── fixtures
│ ├── simple
│ │ ├── src
│ │ │ ├── main
│ │ │ │ ├── index.js
│ │ │ │ └── foo.js
│ │ │ └── renderer
│ │ │ │ ├── foo.html
│ │ │ │ ├── style.css
│ │ │ │ └── index.js
│ │ └── package.json
│ └── typescript
│ │ ├── src
│ │ └── main
│ │ │ └── index.ts
│ │ └── package.json
├── .babelrc
├── tsconfig.json
├── src
│ ├── titleTest.ts
│ ├── loaderDetectionTest.ts
│ ├── test.ts
│ └── helpers
│ │ └── helper.ts
├── out
│ └── __snapshots__
│ │ ├── titleTest.js.snap
│ │ └── loaderDetectionTest.js.snap
└── babel-jest.js
├── .github
├── FUNDING.yml
└── issue_template.md
├── packages
├── electron-webpack
│ ├── webpack.app.config.js
│ ├── webpack.main.config.js
│ ├── webpack.test.config.js
│ ├── webpack.renderer.config.js
│ ├── webpack.renderer.dll.config.js
│ ├── vendor
│ │ └── runnerw.exe
│ ├── vue-renderer-entry.js
│ ├── tsconfig.json
│ ├── src
│ │ ├── electron-main-hmr
│ │ │ ├── webpack-env.d.ts
│ │ │ ├── main-hmr.ts
│ │ │ ├── HmrServer.ts
│ │ │ └── HmrClient.ts
│ │ ├── configurators
│ │ │ ├── vue
│ │ │ │ ├── vue-main-dev-entry.ts
│ │ │ │ └── vue.ts
│ │ │ ├── eslint.ts
│ │ │ ├── dll.ts
│ │ │ ├── js.ts
│ │ │ └── ts.ts
│ │ ├── electron-builder.ts
│ │ ├── cli.ts
│ │ ├── core.ts
│ │ ├── util.ts
│ │ ├── targets
│ │ │ ├── MainTarget.ts
│ │ │ ├── BaseTarget.ts
│ │ │ └── RendererTarget.ts
│ │ ├── config.ts
│ │ ├── dev
│ │ │ ├── devUtil.ts
│ │ │ ├── ChildProcessManager.ts
│ │ │ ├── WebpackDevServerManager.ts
│ │ │ └── dev-runner.ts
│ │ ├── plugins
│ │ │ ├── WatchMatchPlugin.ts
│ │ │ └── WebpackRemoveOldAssetsPlugin.ts
│ │ └── main.ts
│ ├── typings
│ │ ├── crocket.d.ts
│ │ ├── webpack-dev-server.d.ts
│ │ └── yargs.d.ts
│ ├── tsconfig-base.json
│ ├── .yarnclean
│ ├── package.json
│ └── scheme.json
├── electron-webpack-js
│ ├── readme.md
│ └── package.json
├── electron-webpack-vue
│ ├── readme.md
│ └── package.json
├── electron-webpack-eslint
│ ├── readme.md
│ └── package.json
└── electron-webpack-ts
│ ├── readme.md
│ ├── changelog.md
│ └── package.json
├── netlify.toml
├── lerna.json
├── .idea
├── codeStyles
│ ├── codeStyleConfig.xml
│ └── Project.xml
├── encodings.xml
├── vcs.xml
├── jsLinters
│ └── tslint.xml
├── modules.xml
├── misc.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── rc-producer.yml
├── dictionaries
│ └── develar.xml
└── codeStyleSettings.xml
├── npm-publish.sh
├── babel.config.js
├── .gitignore
├── .yarnclean
├── .circleci
└── config.yml
├── docs
├── en
│ ├── extra.js
│ ├── cli-commands.md
│ ├── core-concepts.md
│ ├── using-static-assets.md
│ ├── extending-as-a-library.md
│ ├── dependency-management.md
│ ├── environment-variables.md
│ ├── building.md
│ ├── technical-differences-of-electron-compile.md
│ ├── dll-bundle-splitting.md
│ ├── project-structure.md
│ ├── index.md
│ ├── development.md
│ ├── configuration.md
│ ├── modifying-webpack-configurations.md
│ └── add-ons.md
└── publish.sh
├── electron-webpack.iml
├── mkdocs.yml
├── package.json
└── readme.md
/test/fixtures/simple/src/main/index.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/simple/src/main/foo.js:
--------------------------------------------------------------------------------
1 | console.log("hi")
--------------------------------------------------------------------------------
/test/fixtures/simple/src/renderer/foo.html:
--------------------------------------------------------------------------------
1 |
Hi
--------------------------------------------------------------------------------
/test/fixtures/simple/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Test"
3 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom: https://www.electron.build/donate
2 |
--------------------------------------------------------------------------------
/test/fixtures/typescript/src/main/index.ts:
--------------------------------------------------------------------------------
1 | class A {
2 | readonly foo = 123
3 | }
--------------------------------------------------------------------------------
/packages/electron-webpack/webpack.app.config.js:
--------------------------------------------------------------------------------
1 | module.exports = env => require("./out/main").getAppConfiguration(env)
2 |
--------------------------------------------------------------------------------
/packages/electron-webpack/webpack.main.config.js:
--------------------------------------------------------------------------------
1 | module.exports = env => require("./out/main").getMainConfiguration(env)
--------------------------------------------------------------------------------
/packages/electron-webpack/webpack.test.config.js:
--------------------------------------------------------------------------------
1 | module.exports = env => require("./out/main").getTestConfiguration(env)
--------------------------------------------------------------------------------
/packages/electron-webpack/webpack.renderer.config.js:
--------------------------------------------------------------------------------
1 | module.exports = env => require("./out/main").getRendererConfiguration(env)
2 |
--------------------------------------------------------------------------------
/packages/electron-webpack/webpack.renderer.dll.config.js:
--------------------------------------------------------------------------------
1 | module.exports = env => require("./out/main").getDllConfiguration(env)
2 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [Settings]
2 | ID = "905ec7c5-2006-493e-ac00-d2d81b0bcc56"
3 |
4 | [Build]
5 | Publish = ""
6 | Functions = ""
7 |
--------------------------------------------------------------------------------
/packages/electron-webpack/vendor/runnerw.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/electron-userland/electron-webpack/HEAD/packages/electron-webpack/vendor/runnerw.exe
--------------------------------------------------------------------------------
/test/fixtures/simple/src/renderer/style.css:
--------------------------------------------------------------------------------
1 | .f1 {
2 | background: image("static/a/foo.png")
3 | }
4 |
5 | .f2 {
6 | background: image("static/b/foo.png")
7 | }
--------------------------------------------------------------------------------
/test/fixtures/typescript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Test",
3 | "devDependencies": {
4 | "electron-webpack-ts": "*",
5 | "typescript": "*"
6 | }
7 | }
--------------------------------------------------------------------------------
/packages/electron-webpack/vue-renderer-entry.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue"
2 |
3 | Vue.config.productionTip = false
4 | Vue.config.devtools = process.env.NODE_ENV === "development"
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "lerna": "2.0.0",
3 | "packages": [
4 | "packages/*"
5 | ],
6 | "version": "independent",
7 | "npmClient": "yarn",
8 | "useWorkspaces": true
9 | }
10 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/packages/electron-webpack-js/readme.md:
--------------------------------------------------------------------------------
1 | # electron-webpack-js
2 |
3 | JavaScript add-on for [electron-webpack](https://github.com/electron-userland/electron-webpack). Bundled, no need to install.
--------------------------------------------------------------------------------
/test/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | sourceMaps: "inline",
3 | plugins: [
4 | [
5 | "@babel/plugin-transform-modules-commonjs",
6 | {
7 | lazy: true
8 | }
9 | ]
10 | ]
11 | }
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/packages/electron-webpack-vue/readme.md:
--------------------------------------------------------------------------------
1 | # electron-webpack-vue
2 |
3 | Vue.js add-on for [electron-webpack](https://github.com/electron-userland/electron-webpack).
4 |
5 | ## Install
6 |
7 | Please see [Vue.js](https://webpack.electron.build/add-ons#vuejs).
8 |
--------------------------------------------------------------------------------
/packages/electron-webpack-eslint/readme.md:
--------------------------------------------------------------------------------
1 | # electron-webpack-eslint
2 |
3 | ESLint add-on for [electron-webpack](https://github.com/electron-userland/electron-webpack).
4 |
5 | ### Install
6 |
7 | Please see [ESLint](https://webpack.electron.build/add-ons#eslint).
8 |
--------------------------------------------------------------------------------
/packages/electron-webpack-ts/readme.md:
--------------------------------------------------------------------------------
1 | # electron-webpack-ts
2 |
3 | TypeScript add-on for [electron-webpack](https://github.com/electron-userland/electron-webpack).
4 |
5 | ## Install
6 |
7 | Please see [TypeScript](https://webpack.electron.build/add-ons#typescript).
8 |
--------------------------------------------------------------------------------
/.idea/jsLinters/tslint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../node_modules/ts-babel/tsconfig-base.json",
3 | "compilerOptions": {
4 | "outDir": "out",
5 | "baseUrl": "../packages",
6 | "skipLibCheck": true
7 | },
8 | "declaration": false,
9 | "include": [
10 | "src/**/*.ts"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/packages/electron-webpack/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig-base.json",
3 | "compilerOptions": {
4 | "stripInternal": true,
5 | "outDir": "out",
6 | "skipLibCheck": true
7 | },
8 | "include": [
9 | "**/*.ts", "src/electron-builder.ts", "typings/*.d.ts"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/test/fixtures/simple/src/renderer/index.js:
--------------------------------------------------------------------------------
1 | import "./style.css"
2 | import html from "@/foo.html"
3 | import * as path from "path"
4 |
5 | console.log(html)
6 |
7 | const fileContents = fs.readFileSync(path.join(__static, "/foo.txt")).toString("hex")
8 | console.log(fileContents)
9 |
10 | class A {
11 | }
--------------------------------------------------------------------------------
/packages/electron-webpack/src/electron-main-hmr/webpack-env.d.ts:
--------------------------------------------------------------------------------
1 | declare namespace __WebpackModuleApi {
2 | interface Hot {
3 | check(isAutoApply?: boolean): Promise | null>
4 |
5 | apply(options?: AcceptOptions): Promise>
6 |
7 | status(): "abort" | "fail" | "idle"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/packages/electron-webpack-ts/changelog.md:
--------------------------------------------------------------------------------
1 | # 2.0.0 (2018-03-18)
2 |
3 | ### Features
4 |
5 | * Upgrade `ts-loader` to `4.1.0` (webpack 4 support).
6 |
7 | # 1.1.0 (2017-08-27)
8 | dde84
9 | ### Features
10 |
11 | * Upgrade `ts-loader` to `2.3.4` ([configFile](https://github.com/TypeStrong/ts-loader/pull/607) option is now used by electron-webpack).
--------------------------------------------------------------------------------
/packages/electron-webpack/src/configurators/vue/vue-main-dev-entry.ts:
--------------------------------------------------------------------------------
1 | import installExtension, { VUEJS_DEVTOOLS } from "electron-devtools-installer"
2 |
3 | // install vue-devtools
4 | require("electron").app.on("ready", () => {
5 | installExtension(VUEJS_DEVTOOLS)
6 | .catch(error => {
7 | console.log("Unable to install `vue-devtools`: \n", error)
8 | })
9 | })
--------------------------------------------------------------------------------
/npm-publish.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 |
4 | ln -f readme.md packages/electron-webpack/readme.md
5 |
6 | npm publish packages/electron-webpack || true
7 | npm publish packages/electron-webpack-eslint || true
8 | npm publish packages/electron-webpack-js || true
9 | npm publish packages/electron-webpack-ts || true
10 | npm publish packages/electron-webpack-vue || true
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // bloody babel 7 requires to use ONLY babel.config.js and old convenient key in package.json or .babelrc ARE NOT SUPPORTED ANYMORE
4 | module.exports = function (api) {
5 | // bloody babel 7 requires this string to revalidate cache of config on env change
6 | api.env()
7 | return {
8 | "presets": [
9 | "babel-preset-ts-node8",
10 | ],
11 | }
12 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /packages/*/out/
3 | node_modules/
4 | lerna-debug.log
5 | packages/electron-webpack/readme.md
6 | _book/
7 | #packages/electron-webpack/scheme.json
8 |
9 | # to not exclude .js.snap (jest snapshots)
10 | /test/out/**/*.js
11 | /test/out/**/*.map
12 |
13 | .idea/workspace.xml
14 | .idea/shelf/
15 |
16 | /CHANGELOG.md
17 | yarn-error.log
18 |
19 | .renderer-index-template.html
20 |
21 | /site/
--------------------------------------------------------------------------------
/packages/electron-webpack-eslint/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron-webpack-eslint",
3 | "version": "6.0.0",
4 | "license": "MIT",
5 | "author": "Greg Holguin ",
6 | "files": [],
7 | "repository": "electron-userland/electron-webpack",
8 | "dependencies": {
9 | "babel-eslint": "^10.1.0",
10 | "eslint": "^6.8.0",
11 | "eslint-friendly-formatter": "^4.0.1",
12 | "eslint-loader": "^4.0.0"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/electron-webpack-ts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron-webpack-ts",
3 | "version": "4.0.1",
4 | "license": "MIT",
5 | "author": "Vladimir Krivosheev ",
6 | "files": [],
7 | "repository": "electron-userland/electron-webpack",
8 | "dependencies": {
9 | "fork-ts-checker-webpack-plugin": "^4.1.2",
10 | "ts-loader": "^6.2.2"
11 | },
12 | "peerDependencies": {
13 | "typescript": "^3.8.3"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.github/issue_template.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | * **Version**:
4 |
5 |
6 |
7 |
8 | * **Target**:
9 |
10 |
11 |
--------------------------------------------------------------------------------
/packages/electron-webpack-vue/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron-webpack-vue",
3 | "version": "2.4.0",
4 | "license": "MIT",
5 | "author": "Vladimir Krivosheev ",
6 | "files": [],
7 | "repository": "electron-userland/electron-webpack",
8 | "dependencies": {
9 | "vue-class-component": "^7.2.3",
10 | "vue-html-loader": "^1.2.4",
11 | "vue-loader": "^15.9.1",
12 | "vue-style-loader": "^4.1.2",
13 | "vue-template-compiler": "^2.6.11"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/electron-webpack/typings/crocket.d.ts:
--------------------------------------------------------------------------------
1 | declare module "crocket" {
2 | export interface CrocketConnectOptions {
3 | path: string
4 | }
5 |
6 | export default class Crocket {
7 | connect(options: CrocketConnectOptions, connected: (error?: Error) => void): void
8 |
9 | listen(options: CrocketConnectOptions, handler: (error?: Error) => void): void
10 |
11 | on(name: string, handler: (data: T) => void): void
12 |
13 | emit(name: string, data?: any): void
14 | }
15 | }
--------------------------------------------------------------------------------
/packages/electron-webpack-js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron-webpack-js",
3 | "version": "2.4.1",
4 | "license": "MIT",
5 | "author": "Vladimir Krivosheev ",
6 | "files": [],
7 | "repository": "electron-userland/electron-webpack",
8 | "dependencies": {
9 | "@babel/core": "^7.9.0",
10 | "babel-loader": "^8.1.0",
11 | "@babel/plugin-syntax-dynamic-import": "^7.8.3",
12 | "@babel/preset-env": "^7.9.0",
13 | "babel-plugin-component": "^1.1.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/rc-producer.yml:
--------------------------------------------------------------------------------
1 | - &defaults
2 | files: ["test/src/**/*", "!**/helpers/**/*"]
3 | script: "node_modules/jest-cli/bin/jest.js"
4 | scriptArgs: ["-i", &filePattern '--testPathPattern=[/\\]{1}${fileNameWithoutExt}\.\w+$']
5 | rcName: "${fileNameWithoutExt}"
6 |
7 | -
8 | <<: *defaults
9 | lineRegExp: '^\s*(?:test|it|testAndIgnoreApiRate)(?:\.[\w.]+)?\("([^"'']+)'
10 | scriptArgs: ["-i", "-t", "${0regExp}", *filePattern]
11 | rcName: "${fileNameWithoutExt}.${0}"
12 | env:
13 | # DEBUG: electron-webpack*
--------------------------------------------------------------------------------
/packages/electron-webpack/tsconfig-base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2017",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 |
7 | "strict": true,
8 | "noUnusedLocals": true,
9 | "noFallthroughCasesInSwitch": true,
10 | "noImplicitReturns": true,
11 |
12 | "inlineSources": true,
13 | "sourceMap": true,
14 |
15 | "allowSyntheticDefaultImports": true,
16 | "experimentalDecorators": true,
17 |
18 | "newLine": "lf",
19 |
20 | "noEmitOnError": true
21 | }
22 | }
--------------------------------------------------------------------------------
/.yarnclean:
--------------------------------------------------------------------------------
1 | # test directories
2 | __tests__
3 | node_modules/*/test
4 | node_modules/*/tests
5 | powered-test
6 |
7 | # asset directories
8 | docs
9 | doc
10 |
11 | # examples
12 | example
13 | examples
14 |
15 | # code coverage directories
16 | coverage
17 | .nyc_output
18 |
19 | # build scripts
20 | Makefile
21 | Gulpfile.js
22 | Gruntfile.js
23 |
24 | # configs
25 | .tern-project
26 | .gitattributes
27 | .editorconfig
28 | .*ignore
29 | .eslintrc
30 | .jshintrc
31 | .flowconfig
32 | .documentup.json
33 | .yarn-metadata.json
34 |
35 | # misc
36 | *.gz
37 | *.md
38 |
--------------------------------------------------------------------------------
/packages/electron-webpack/.yarnclean:
--------------------------------------------------------------------------------
1 | # test directories
2 | __tests__
3 | node_modules/*/test
4 | node_modules/*/tests
5 | powered-test
6 |
7 | # asset directories
8 | docs
9 | doc
10 |
11 | # examples
12 | example
13 | examples
14 |
15 | # code coverage directories
16 | coverage
17 | .nyc_output
18 |
19 | # build scripts
20 | Makefile
21 | Gulpfile.js
22 | Gruntfile.js
23 |
24 | # configs
25 | .tern-project
26 | .gitattributes
27 | .editorconfig
28 | .*ignore
29 | .eslintrc
30 | .jshintrc
31 | .flowconfig
32 | .documentup.json
33 | .yarn-metadata.json
34 |
35 | # misc
36 | *.gz
37 | *.md
38 |
--------------------------------------------------------------------------------
/packages/electron-webpack/src/electron-main-hmr/main-hmr.ts:
--------------------------------------------------------------------------------
1 | require("source-map-support/source-map-support.js").install()
2 |
3 | const socketPath = process.env.ELECTRON_HMR_SOCKET_PATH!
4 | if (socketPath == null) {
5 | throw new Error(`[HMR] Env ELECTRON_HMR_SOCKET_PATH is not set`)
6 | }
7 |
8 | // module, but not relative path must be used (because this file is used as entry)
9 | const HmrClient = require("electron-webpack/out/electron-main-hmr/HmrClient").HmrClient
10 | // tslint:disable:no-unused-expression
11 | new HmrClient(socketPath, (module as any).hot, () => {
12 | return __webpack_hash__
13 | })
--------------------------------------------------------------------------------
/.idea/dictionaries/develar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | crocket
5 | dotenv
6 | eaddrinuse
7 | embeddedjs
8 | errored
9 | gitbook
10 | lesscss
11 | minification
12 | netlify
13 | nunjucks
14 | runnerw
15 | sigusr
16 | transpilation
17 | transpiling
18 | typescriptlang
19 | undelayed
20 | vscode
21 |
22 |
23 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | jobs:
4 | build:
5 | docker:
6 | - image: circleci/node:10
7 | environment:
8 | JEST_JUNIT_OUTPUT: ./test-reports/test.xml
9 | steps:
10 | - checkout
11 | - restore_cache:
12 | keys:
13 | - dependencies-{{ checksum "yarn.lock" }}
14 | - run:
15 | command: yarn
16 | - save_cache:
17 | key: dependencies-{{ checksum "yarn.lock" }}
18 | paths:
19 | - node_modules
20 | - run:
21 | command: yarn test -- --ci --testResultsProcessor="jest-junit"
22 | - store_test_results:
23 | path: test-reports
--------------------------------------------------------------------------------
/docs/en/extra.js:
--------------------------------------------------------------------------------
1 | "use strict"
2 |
3 | // document.addEventListener("DOMContentLoaded", () => {
4 | loadNavPane()
5 | // })
6 |
7 | function loadNavPane() {
8 | let nav = document.getElementsByClassName("md-nav")
9 | for (let i = 0; i < nav.length; i++) {
10 | const item = nav.item(i)
11 | if (typeof item.style === "undefined") {
12 | continue;
13 | }
14 |
15 | if (item.getAttribute("data-md-level") && item.getAttribute("data-md-component")) {
16 | item.style.display = 'block'
17 | item.style.overflow = 'visible'
18 | }
19 | }
20 |
21 | nav = document.getElementsByClassName("md-nav__toggle")
22 | for (let i = 0; i < nav.length; i++) {
23 | nav.item(i).checked = true;
24 | }
25 | }
--------------------------------------------------------------------------------
/electron-webpack.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/packages/electron-webpack/src/configurators/eslint.ts:
--------------------------------------------------------------------------------
1 | import { WebpackConfigurator } from "../main"
2 |
3 | export function configureEslint(configurator: WebpackConfigurator) {
4 | const hasPreset = configurator.hasDevDependency("electron-webpack-eslint")
5 | if (!(hasPreset || (configurator.hasDevDependency("eslint") && configurator.hasDevDependency("eslint-loader")))) {
6 | return
7 | }
8 |
9 | const options: { [name: string]: any } = {
10 | cwd: configurator.projectDir
11 | }
12 | if (hasPreset || configurator.hasDevDependency("eslint-friendly-formatter")) {
13 | options.formatter = require("eslint-friendly-formatter")
14 | }
15 |
16 | configurator.rules.push({
17 | test: /\.(jsx?|tsx?|vue)$/,
18 | enforce: "pre",
19 | exclude: /node_modules/,
20 | loader: "eslint-loader",
21 | options
22 | })
23 | }
--------------------------------------------------------------------------------
/test/src/titleTest.ts:
--------------------------------------------------------------------------------
1 | import * as path from "path"
2 | import { bufferToString, rootDir, testWebpack } from "./helpers/helper"
3 |
4 | test("title true", () => testTitle(true))
5 | test("title false", () => testTitle(false))
6 | test("title null", () => testTitle(null))
7 |
8 | const rendererConfig = require("electron-webpack/webpack.renderer.config")
9 |
10 | async function testTitle(title: boolean | null) {
11 | const projectDir = path.join(rootDir, "test/fixtures/simple")
12 | const configuration = await rendererConfig({
13 | production: true,
14 | minify: false,
15 | configuration: {
16 | projectDir,
17 | title
18 | },
19 | })
20 | const fs = await testWebpack(configuration, projectDir, false)
21 | expect(bufferToString(fs.meta(`${projectDir}/dist/renderer/index.html`), projectDir).toString()).toMatchSnapshot()
22 | }
--------------------------------------------------------------------------------
/docs/en/cli-commands.md:
--------------------------------------------------------------------------------
1 | `electron-webpack` comes with a simple CLI to help run your application in development and compile your source code. Here is a general overview of what's available.
2 |
3 | #### `electron-webpack dev`
4 | Run application in development mode.
5 |
6 | #### `electron-webpack [config]`
7 | Compile source code of specific configuration. Can be `main`, `renderer`, `renderer-dll`, or `app` (default).
8 |
9 | #### `electron-webpack --help`
10 | Yields quick overview of all CLI features.
11 |
12 | ---
13 |
14 | ### Package Scripts
15 | To make things easier, make use of setting up scripts in your `package.json` to use the CLI easier.
16 |
17 | ```json tab="package.json"
18 | {
19 | "scripts": {
20 | "dev": "electron-webpack dev",
21 | "compile": "electron-webpack"
22 | }
23 | }
24 | ```
25 |
26 | Now you can run with `yarn dev` & `yarn compile`.
27 |
--------------------------------------------------------------------------------
/docs/publish.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 |
4 | # netlify-cli can be used to publish directory directly without intermediate git repository,
5 | # but it will complicate deployment because in this case you need to be authenticated on Netlify.
6 | # But for what, if you are already configure Git access?
7 | # Also, this approach allows us to manage access using GitHub and easily give access to publish to project members.
8 |
9 |
10 | # https://stackoverflow.com/questions/3311774/how-to-convert-existing-non-empty-directory-into-a-git-working-directory-and-pus
11 |
12 | cd _book
13 |
14 | # do not use force push - netlify doesn't trigger deploy for forced push
15 | git clone --no-checkout --branch en --single-branch git@github.com:develar/generated-gitbook-electron-webpack.git ./repo.tmp
16 | mv ./repo.tmp/.git ./
17 | rmdir ./repo.tmp
18 | git add --all .
19 | git commit -m "update"
20 | git push
--------------------------------------------------------------------------------
/packages/electron-webpack/src/electron-builder.ts:
--------------------------------------------------------------------------------
1 | import { Lazy } from "lazy-val"
2 | import { getElectronWebpackConfiguration } from "./config"
3 |
4 | interface Context {
5 | projectDir: string
6 | packageMetadata: Lazy<{ [key: string]: any } | null> | null
7 | }
8 |
9 | export default async function(context: Context) {
10 | const electronWebpackConfig = await getElectronWebpackConfiguration(context)
11 | const distDir = electronWebpackConfig.commonDistDirectory!!
12 | return {
13 | extraMetadata: {
14 | main: "main.js"
15 | },
16 | files: [
17 | {
18 | from: ".",
19 | filter: ["package.json"]
20 | },
21 | {
22 | from: `${distDir}/main`
23 | },
24 | {
25 | from: `${distDir}/renderer`
26 | },
27 | {
28 | from: `${distDir}/renderer-dll`
29 | }
30 | ],
31 | extraResources: [
32 | {
33 | from: electronWebpackConfig.staticSourceDirectory,
34 | to: electronWebpackConfig.staticSourceDirectory
35 | }
36 | ]
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/electron-webpack/src/electron-main-hmr/HmrServer.ts:
--------------------------------------------------------------------------------
1 | import Crocket from "crocket"
2 | import { Stats } from "webpack"
3 |
4 | const debug = require("debug")("electron-webpack:dev-runner")
5 |
6 | export class HmrServer {
7 | private state = false
8 | readonly ipc = new Crocket()
9 |
10 | listen(): Promise {
11 | return new Promise((resolve, reject) => {
12 | const socketPath = `/tmp/electron-main-ipc-${process.pid.toString(16)}.sock`
13 | this.ipc.listen({path: socketPath}, error => {
14 | if (error != null) {
15 | reject(error)
16 | }
17 | if (debug.enabled) {
18 | debug(`HMR Server listening on ${socketPath}`)
19 | }
20 | resolve(socketPath)
21 | })
22 | })
23 | }
24 |
25 | beforeCompile() {
26 | this.state = false
27 | }
28 |
29 | built(stats: Stats): void {
30 | this.state = true
31 | setImmediate(() => {
32 | if (!this.state) {
33 | return
34 | }
35 |
36 | const hash = stats.toJson({assets: false, chunks: false, children: false, modules: false}).hash
37 | if (debug.enabled) {
38 | debug(`Send built: hash ${hash}`)
39 | }
40 | this.ipc.emit("/built", {hash})
41 | })
42 | }
43 | }
--------------------------------------------------------------------------------
/docs/en/core-concepts.md:
--------------------------------------------------------------------------------
1 | Below is a general overview of what `electron-webpack` aims to solve, and what it isn't.
2 |
3 | #### `electron-webpack` is a module
4 | It is not a fully featured boilerplate; it is a single **updatable** package. It includes many `webpack` configurations to help jump-start your development needs. If you are looking for a *boilerplate* to get started, please see [electron-webpack-quick-start](https://github.com/electron-userland/electron-webpack-quick-start).
5 |
6 | #### `electron-webpack` has a CLI
7 | You can take use of a few useful commands such as running in development and compiling your source code ([more info](./cli-commands.md)).
8 |
9 | #### `electron-webpack` can be extended
10 | By using `electron-webpack` you are **not** restricted to any sort of API abstraction of `webpack`. Although there are [Add-ons](./add-ons.md) made available to simplify smaller tasks, the entirety of `webpack`'s documentation is fully applicable ([more info](./extending-as-a-library.md)).
11 |
12 | #### `electron-webpack` is agnostic
13 | Aside from setting up core `webpack` configurations with `@babel/preset-env` and making optimizations specific to the `electron` environment, `electron-webpack` does its best to not impose or encourage any sort of project structure or build cycle. Just as stated before, this is a module and can be used as tool outside of its CLI.
14 |
--------------------------------------------------------------------------------
/packages/electron-webpack/typings/webpack-dev-server.d.ts:
--------------------------------------------------------------------------------
1 | declare module "webpack-dev-server" {
2 | // import * as core from "express-serve-static-core"
3 | import * as http from "http"
4 | import * as webpack from "webpack"
5 |
6 | namespace WebpackDevServer {
7 | export interface Configuration {
8 | contentBase?: string
9 | hot?: boolean
10 | https?: boolean
11 | historyApiFallback?: boolean
12 | compress?: boolean
13 | proxy?: any
14 | staticOptions?: any
15 | quiet?: boolean
16 | noInfo?: boolean
17 | lazy?: boolean
18 | filename?: string | RegExp
19 | watchOptions?: webpack.WatchOptions
20 | publicPath?: string
21 | headers?: any
22 | stats?: webpack.compiler.StatsOptions | webpack.compiler.StatsToStringOptions
23 | public?: string
24 | disableHostCheck?: boolean
25 |
26 | // setup?(app: core.Express, ctx: any): void
27 | }
28 |
29 | export interface WebpackDevServer {
30 | new (webpack: webpack.Compiler,
31 | config: Configuration): WebpackDevServer
32 |
33 | listen(port: number,
34 | hostname: string,
35 | callback?: Function): http.Server
36 |
37 | listen(port: number,
38 | callback?: Function): http.Server
39 | }
40 | }
41 |
42 | const wds: WebpackDevServer.WebpackDevServer
43 | export default wds
44 | }
--------------------------------------------------------------------------------
/test/out/__snapshots__/titleTest.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`title false 1`] = `
4 | "
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | "
13 | `;
14 |
15 | exports[`title null 1`] = `
16 | "
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | "
25 | `;
26 |
27 | exports[`title true 1`] = `
28 | "
29 |
30 |
31 |
32 | Test
33 |
34 |
35 |
36 | "
37 | `;
38 |
--------------------------------------------------------------------------------
/packages/electron-webpack/src/configurators/vue/vue.ts:
--------------------------------------------------------------------------------
1 | import * as path from "path"
2 | import { WebpackConfigurator } from "../../main"
3 |
4 | export function configureVue(configurator: WebpackConfigurator) {
5 | if (!configurator.hasDependency("vue")) {
6 | return
7 | }
8 |
9 | configurator.extensions.push(".vue")
10 |
11 | Object.assign(configurator.config.resolve!!.alias, {
12 | vue$: "vue/dist/vue.esm.js",
13 | "vue-router$": "vue-router/dist/vue-router.esm.js",
14 | })
15 |
16 | if (!configurator.isProduction && configurator.type === "main") {
17 | configurator.entryFiles.push(path.join(__dirname, "vue-main-dev-entry.js"))
18 | }
19 | }
20 |
21 | export function configureVueRenderer(configurator: WebpackConfigurator) {
22 | configurator.entryFiles.push(path.join(__dirname, "../../../vue-renderer-entry.js"))
23 |
24 | configurator.debug("Vue detected")
25 | configurator.rules.push(
26 | {
27 | test: /\.html$/,
28 | use: "vue-html-loader"
29 | },
30 | {
31 | test: /\.vue$/,
32 | use: {
33 | loader: "vue-loader",
34 | options: {
35 | loaders: {
36 | sass: "vue-style-loader!css-loader!sass-loader?indentedSyntax=1",
37 | scss: "vue-style-loader!css-loader!sass-loader",
38 | }
39 | }
40 | }
41 | },
42 | )
43 |
44 | const VueLoaderPlugin = require("vue-loader/lib/plugin")
45 | configurator.plugins.push(new VueLoaderPlugin())
46 | }
--------------------------------------------------------------------------------
/packages/electron-webpack/src/cli.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import * as path from "path"
4 | import yargs from "yargs"
5 |
6 | // not strict because we allow to pass any webpack args
7 | // tslint:disable-next-line:no-unused-expression
8 | yargs
9 | .command(["app", "compile", "*"], "Compile application", yargs => yargs, argv => build("app"))
10 | .command(["main"], "Compile main process", yargs => yargs, argv => build("main"))
11 | .command(["renderer"], "Compile renderer process", yargs => yargs, argv => build("renderer"))
12 | .command(["dll"], "Compile DLL bundles", yargs => yargs, argv => build("renderer.dll"))
13 | .command(["dev"], "Run a development mode", yargs => yargs, argv => runInDevMode())
14 | .argv
15 |
16 | function build(configFile: string) {
17 | const args = process.argv
18 | // if command `electron-webpack` - remove first 2 args, if `electron-webpack compile` (or any other subcommand name) - 3
19 | const sliceIndex = args.length > 2 && !args[2].startsWith("-") ? 3 : 2
20 | const extraWebpackArgs = sliceIndex < args.length ? args.slice(sliceIndex) : []
21 | // remove extra args
22 | args.length = 2
23 | if (!extraWebpackArgs.some(it => it.includes("--env.production"))) {
24 | args.push("--env.production")
25 | }
26 | args.push("--progress")
27 | args.push(...extraWebpackArgs)
28 | args.push("--config", path.join(__dirname, "..", `webpack.${configFile}.config.js`))
29 |
30 | require("yargs")(args.slice(2))
31 | require(require.resolve("webpack-cli"))
32 | }
33 |
34 | function runInDevMode() {
35 | require("./dev/dev-runner")
36 | }
--------------------------------------------------------------------------------
/docs/en/using-static-assets.md:
--------------------------------------------------------------------------------
1 | When using `webpack` to bundle all of our assets together we lose the ability to provide a full path to our assets. This is especially important when we need to use modules like `fs` or those that require a file path to an asset. `electron-webpack` is aware of that issue and provides a solution.
2 |
3 | You may have noticed in [Project Structure](./project-structure.md) there is a directory specifically for static assets (`static/`). It is here where we can put assets we explicity don't want `webpack` to bundle. So now how can we access their path?
4 |
5 | ### Using the `__static` variable
6 |
7 | Similar to how `__dirname` can provide you with a path to the parent directory name when working in a Node.js environment, `__static` is made available to provide you a path to your `static/` folder. This variable is available in both `development` and `production`.
8 |
9 | #### Use Case
10 |
11 | Let's say we have a static Text file (`foobar.txt`) we need to read into our application using `fs`. Here's how we can use the `__static` variable to get a reliable path to our asset.
12 |
13 | ```txt tab="static/foobar.txt"
14 | foobarbizzbuzz
15 | ```
16 |
17 | ##### someScript.js (`main` or `renderer` process)
18 | ```js
19 | import fs from 'fs'
20 | import path from 'path'
21 |
22 | /* use `path` to create the full path to our asset */
23 | const pathToAsset = path.join(__static, '/foobar.txt')
24 |
25 | /* use `fs` to consume the path and read our asset */
26 | const fileContents = fs.readFileSync(pathToAsset, 'utf8')
27 |
28 | console.log(fileContents)
29 | // => "foobarbizzbuzz"
30 | ```
31 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: electron-webpack
2 | use_directory_urls: false
3 | theme:
4 | name: material
5 | logo:
6 | icon: 'build'
7 | palette:
8 | primary: 'blue'
9 | accent: 'blue'
10 |
11 | docs_dir: docs/en
12 |
13 | repo_name: electron-userland/electron-webpack
14 | repo_url: 'https://github.com/electron-userland/electron-webpack'
15 | edit_uri: ""
16 | dev_addr: 0.0.0.0:8000
17 | strict: true
18 |
19 | extra_javascript:
20 | - extra.js
21 |
22 | markdown_extensions:
23 | - admonition
24 | - smarty
25 | - pymdownx.smartsymbols
26 | - pymdownx.details
27 | - pymdownx.inlinehilite
28 | - pymdownx.superfences
29 | - markdown_include.include:
30 | base_path: docs/en
31 | - toc:
32 | permalink: true
33 |
34 | google_analytics:
35 | - 'UA-104881670-1'
36 | - webpack.electron.build
37 |
38 | nav:
39 | - Introduction: index.md
40 | - Core Concepts: core-concepts.md
41 | - Development:
42 | - Development: development.md
43 | - Add-ons: add-ons.md
44 | - Configuration: configuration.md
45 | - CLI Commands: cli-commands.md
46 | - Dll Bundle Splitting: dll-bundle-splitting.md
47 | - Environment Variables: environment-variables.md
48 | - Extending as a Library: extending-as-a-library.md
49 | - Modifying Webpack Configurations: modifying-webpack-configurations.md
50 | - Using Static Assets: using-static-assets.md
51 | - Project Structure: project-structure.md
52 | - Building: building.md
53 | - Miscellaneous:
54 | - Dependency Management: dependency-management.md
55 | - Technical Differences of electron-compile: technical-differences-of-electron-compile.md
--------------------------------------------------------------------------------
/packages/electron-webpack/src/core.ts:
--------------------------------------------------------------------------------
1 | export interface PackageMetadata {
2 | name?: string
3 |
4 | dependencies: { [key: string]: any }
5 | devDependencies: { [key: string]: any }
6 |
7 | electronWebpack?: ElectronWebpackConfiguration | null
8 | }
9 |
10 | export interface ElectronWebpackConfiguration {
11 | whiteListedModules?: Array
12 | externals?: Array
13 | electronVersion?: string
14 |
15 | renderer?: ElectronWebpackConfigurationRenderer | null
16 | main?: ElectronWebpackConfigurationMain | null
17 |
18 | staticSourceDirectory?: string | null
19 | commonSourceDirectory?: string | null
20 | commonDistDirectory?: string | null
21 |
22 | title?: string | boolean | null
23 |
24 | projectDir?: string | null
25 | }
26 |
27 | export type ConfigurationType = "main" | "renderer" | "renderer-dll" | "test"
28 |
29 | export interface PartConfiguration {
30 | sourceDirectory?: string | null
31 | }
32 |
33 | export interface ElectronWebpackConfigurationRenderer extends PartConfiguration {
34 | dll?: Array | { [key: string]: any } | null
35 | webpackConfig?: string | null
36 | webpackDllConfig?: string | null
37 | template?: string | null
38 | }
39 |
40 | export interface ElectronWebpackConfigurationMain extends PartConfiguration {
41 | /**
42 | * The extra [entry points](https://webpack.js.org/concepts/entry-points/).
43 | */
44 | extraEntries?: Array | { [key: string]: string | Array } | string
45 | webpackConfig?: string | null
46 | }
47 |
48 | export interface ConfigurationEnv {
49 | production?: boolean
50 | autoClean?: boolean
51 |
52 | minify?: boolean
53 |
54 | forkTsCheckerLogger?: any
55 |
56 | configuration?: ElectronWebpackConfiguration
57 | }
58 |
--------------------------------------------------------------------------------
/packages/electron-webpack/src/util.ts:
--------------------------------------------------------------------------------
1 | import * as BluebirdPromise from "bluebird"
2 | import { stat, Stats } from "fs-extra"
3 | import { createServer } from "net"
4 | import * as path from "path"
5 |
6 | export async function statOrNull(file: string): Promise {
7 | return orNullIfFileNotExist(stat(file))
8 | }
9 |
10 | export function orNullIfFileNotExist(promise: Promise): Promise {
11 | return orIfFileNotExist(promise, null)
12 | }
13 |
14 | export function orIfFileNotExist(promise: Promise, fallbackValue: T): Promise {
15 | return promise
16 | .catch(e => {
17 | if (e.code === "ENOENT" || e.code === "ENOTDIR") {
18 | return fallbackValue
19 | }
20 | throw e
21 | })
22 | }
23 |
24 | export function getFirstExistingFile(names: Array, rootDir: string | null): Promise {
25 | return BluebirdPromise.filter(names.map(it => rootDir == null ? it : path.join(rootDir, it)), it => statOrNull(it).then(it => it != null))
26 | .then(it => it.length > 0 ? it[0] : null)
27 | }
28 |
29 | export function getFreePort(defaultHost: string, defaultPort: number) {
30 | return new Promise((resolve, reject) => {
31 | const server = createServer({pauseOnConnect: true})
32 | server.addListener("listening", () => {
33 | const port = (server.address() as any).port
34 | server.close(() => resolve(port))
35 | })
36 |
37 | function doListen(port: number) {
38 | server.listen({
39 | host: defaultHost,
40 | port,
41 | backlog: 1,
42 | exclusive: true
43 | })
44 | }
45 |
46 | server.on("error", e => {
47 | if ((e as any).code === "EADDRINUSE") {
48 | server.close(() => doListen(0))
49 | }
50 | else {
51 | reject(e)
52 | }
53 | })
54 |
55 | doListen(defaultPort)
56 | })
57 | }
--------------------------------------------------------------------------------
/packages/electron-webpack/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron-webpack",
3 | "version": "2.8.2",
4 | "license": "MIT",
5 | "author": "Vladimir Krivosheev ",
6 | "main": "out/main.js",
7 | "files": [
8 | "out",
9 | "*.js",
10 | "scheme.json",
11 | "tsconfig-base.json",
12 | "vendor"
13 | ],
14 | "bin": {
15 | "electron-webpack": "./out/cli.js"
16 | },
17 | "repository": "electron-userland/electron-webpack",
18 | "engines": {
19 | "node": ">=10.0"
20 | },
21 | "//": "@types/webpack-env in the dependencies because used by clients",
22 | "dependencies": {
23 | "@svgr/webpack": "^5.5.0",
24 | "@types/webpack-env": "^1.15.1",
25 | "async-exit-hook": "^2.0.1",
26 | "bluebird": "^3.7.2",
27 | "chalk": "^4.0.0",
28 | "crocket": "^0.9.11",
29 | "css-hot-loader": "^1.4.4",
30 | "css-loader": "^3.4.2",
31 | "debug": "^4.1.1",
32 | "dotenv": "^8.2.0",
33 | "dotenv-expand": "^5.1.0",
34 | "electron-devtools-installer": "^2.2.4",
35 | "electron-webpack-js": "~2.4.1",
36 | "file-loader": "^6.0.0",
37 | "fs-extra": "^9.0.0",
38 | "html-loader": "^1.1.0",
39 | "html-webpack-plugin": "^4.0.4",
40 | "lazy-val": "^1.0.4",
41 | "mini-css-extract-plugin": "^0.9.0",
42 | "node-loader": "^0.6.0",
43 | "read-config-file": "~4.0.1",
44 | "semver": "^7.1.3",
45 | "source-map-support": "^0.5.16",
46 | "style-loader": "^1.1.3",
47 | "terser-webpack-plugin": "^2.3.5",
48 | "url-loader": "^4.0.0",
49 | "webpack-cli": "^3.3.11",
50 | "webpack-dev-server": "^3.10.3",
51 | "webpack-merge": "^4.2.2",
52 | "yargs": "^15.3.1"
53 | },
54 | "peerDependencies": {
55 | "webpack": "^4.42.1"
56 | },
57 | "resolutions": {
58 | "js-yaml": "^3.13.1",
59 | "htmlnano": "^0.2.5"
60 | },
61 | "types": "./out/main.d.ts",
62 | "publishConfig": {
63 | "tag": "next"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/packages/electron-webpack/src/targets/MainTarget.ts:
--------------------------------------------------------------------------------
1 | import * as path from "path"
2 | import { BannerPlugin, DefinePlugin } from "webpack"
3 | import { WebpackConfigurator } from "../main"
4 | import { getFirstExistingFile } from "../util"
5 | import { BaseTarget, configureFileLoader } from "./BaseTarget"
6 |
7 | export class MainTarget extends BaseTarget {
8 | constructor() {
9 | super()
10 | }
11 |
12 | configureRules(configurator: WebpackConfigurator): void {
13 | super.configureRules(configurator)
14 |
15 | configurator.rules.push({
16 | test: /\.(png|jpg|gif)$/,
17 | use: [
18 | {
19 | loader: "url-loader",
20 | // to avoid any issues related to asar, embed any image up to 10MB as data url
21 | options: configureFileLoader("imgs", 10 * 1024 * 1024),
22 | }
23 | ]
24 | })
25 | }
26 |
27 | async configurePlugins(configurator: WebpackConfigurator): Promise {
28 | await BaseTarget.prototype.configurePlugins.call(this, configurator)
29 |
30 | if (configurator.isProduction) {
31 | configurator.plugins.push(new DefinePlugin({
32 | __static: `process.resourcesPath + "/${configurator.staticSourceDirectory}"`
33 | }))
34 |
35 | // do not add for main dev (to avoid adding to hot update chunks), our main-hmr install it
36 | configurator.plugins.push(new BannerPlugin({
37 | banner: 'require("source-map-support/source-map-support.js").install();',
38 | test: /\.js$/,
39 | raw: true,
40 | entryOnly: true,
41 | }))
42 | return
43 | }
44 |
45 | configurator.entryFiles.push(path.join(__dirname, "../electron-main-hmr/main-hmr"))
46 | const devIndexFile = await getFirstExistingFile(["index.dev.ts", "index.dev.js"], path.join(configurator.projectDir, "src/main"))
47 | if (devIndexFile != null) {
48 | configurator.entryFiles.push(devIndexFile)
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/docs/en/extending-as-a-library.md:
--------------------------------------------------------------------------------
1 | One of the great benefits of using `electron-webpack` is that the entirity of `webpack`'s documentation still applies. You can easily skip using the provided CLI and modify the configurations to meet your needs. Since `electron` uses a multi-process architecture, there are also multiple `webpack` configurations. Let's cover what's provided.
2 |
3 | ### Configuration Files
4 |
5 | * `electron-webpack/webpack.main.config.js` (`main` process)
6 | * `electron-webpack/webpack.renderer.config.js` (`renderer` process)
7 | * `electron-webpack/webpack.renderer.dll.config.js` (Dll bundle spliting)
8 |
9 | If you are wanting to look at these configurations internally, you can easily do the following to print them into your console. Notice that each configuration returns a `Promise`.
10 |
11 | ```js
12 | const webpackMain = require('electron-webpack/webpack.main.config.js')
13 | const { inspect } = require('util')
14 |
15 | webpackMain().then(config => {
16 | console.log(inspect(config, {
17 | showHidden: false,
18 | depth: null,
19 | colors: true
20 | }))
21 | })
22 | ```
23 |
24 | ## Adding a Custom Loader/Plugin
25 |
26 | Let's say we need to support `*.txt` files in our `renderer` process and want to use the `raw-loader` to do so, here's how we can get that setup.
27 |
28 | ##### Install `raw-loader`
29 | ```bash
30 | yarn add -D raw-loader
31 | ```
32 |
33 | ```js tab="myCustomWebpack.renderer.config.js"
34 | const webpackRenderer = require('electron-webpack/webpack.renderer.config.js')
35 |
36 | module.exports = env => {
37 | return new Promise((resolve, reject) => {
38 |
39 | /* get provided config */
40 | webpackRenderer(env).then(rendererConfig => {
41 |
42 | /* add `raw-loader` */
43 | rendererConfig.module.rules.push({
44 | test: /\.txt$/,
45 | use: 'raw-loader'
46 | })
47 |
48 | /* return modified config to webpack */
49 | resolve(rendererConfig)
50 | })
51 | })
52 | }
53 | ```
54 |
55 | Now with your new custom webpack configuration file, you can use the native [`webpack` CLI](https://webpack.js.org/api/cli/).
56 |
57 | ```bash
58 | webpack --config myCustomWebpack.renderer.config.js
59 | ```
60 |
--------------------------------------------------------------------------------
/packages/electron-webpack/src/configurators/dll.ts:
--------------------------------------------------------------------------------
1 | import { readdir, readJson } from "fs-extra"
2 | import * as path from "path"
3 | import { DllPlugin, DllReferencePlugin } from "webpack"
4 | import { WebpackConfigurator } from "../main"
5 | import { orNullIfFileNotExist, statOrNull } from "../util"
6 |
7 | export async function configureDll(configurator: WebpackConfigurator): Promise {
8 | let dllManifest: string | null = null
9 | const projectDir = configurator.projectDir
10 |
11 | if (configurator.type === "renderer-dll") {
12 | const dll = configurator.electronWebpackConfiguration.renderer!!.dll
13 | if (dll == null) {
14 | throw new Error(`renderer-dll requires DLL configuration`)
15 | }
16 |
17 | configurator.config.entry = Array.isArray(dll) ? {vendor: dll} : dll
18 |
19 | dllManifest = path.join(configurator.commonDistDirectory, configurator.type, "manifest.json")
20 | configurator.plugins.push(new DllPlugin({
21 | name: "[name]",
22 | path: dllManifest,
23 | context: projectDir,
24 | }))
25 |
26 | const output = configurator.config.output!
27 | // leave as default "var"
28 | delete output.libraryTarget
29 | output.library = "[name]"
30 | }
31 | else if (configurator.type === "renderer") {
32 | const dllDir = path.join(configurator.commonDistDirectory, "renderer-dll")
33 | const dirStat = await statOrNull(dllDir)
34 | if (dirStat == null || !dirStat.isDirectory()) {
35 | configurator.debug("No DLL directory")
36 | return null
37 | }
38 |
39 | configurator.debug(`DLL directory: ${dllDir}`)
40 | configurator.plugins.push(new DllReferencePlugin({
41 | context: projectDir,
42 | manifest: await readJson(path.join(dllDir, "manifest.json")),
43 | }))
44 | }
45 |
46 | return dllManifest
47 | }
48 |
49 | export async function getDllAssets(dllDir: string, configurator: WebpackConfigurator) {
50 | if (configurator.electronWebpackConfiguration.renderer!!.dll == null) {
51 | return []
52 | }
53 |
54 | const files = await orNullIfFileNotExist(readdir(dllDir))
55 | return files == null ? [] : files.filter(it => it.endsWith(".js") || it.endsWith(".css")).sort()
56 | }
--------------------------------------------------------------------------------
/test/src/loaderDetectionTest.ts:
--------------------------------------------------------------------------------
1 | import { writeFile, writeJson } from "fs-extra"
2 | import * as path from "path"
3 | import { assertThat, getMutableProjectDir, testWebpack } from "./helpers/helper"
4 |
5 | test("nunjucks", async () => {
6 | const projectDir = await getMutableProjectDir()
7 | await writeJson(path.join(projectDir, "package.json"), {
8 | name: "Test",
9 | devDependencies: {
10 | "nunjucks-loader": "*"
11 | }
12 | })
13 | await writeFile(path.join(projectDir, "src/main/page.njk"), "myGlobal = {{ myGlobal }}")
14 | await writeFile(path.join(projectDir, "src/main/index.js"), 'import "./page.njk"')
15 | const configuration = await require("electron-webpack/webpack.main.config.js")({production: true, configuration: {
16 | projectDir,
17 | }})
18 |
19 | return await assertThat(testWebpack(configuration, projectDir)).throws(projectDir)
20 | })
21 |
22 | test("sass", async () => {
23 | const projectDir = await getMutableProjectDir()
24 | await writeFile(path.join(projectDir, "src/renderer/file.scss"), `
25 | $font-stack: Helvetica, sans-serif;
26 | $primary-color: #333;
27 |
28 | body {
29 | font: 100% $font-stack;
30 | color: $primary-color;
31 | }`)
32 | await writeFile(path.join(projectDir, "src/renderer/index.js"), 'import "./file.scss"')
33 | const configuration = await require("electron-webpack/webpack.renderer.config.js")({production: true, configuration: {
34 | projectDir,
35 | }})
36 |
37 | return await assertThat(testWebpack(configuration, projectDir)).throws(projectDir)
38 | })
39 |
40 | test("react", async () => {
41 | const projectDir = await getMutableProjectDir()
42 | await writeJson(path.join(projectDir, "package.json"), {
43 | name: "Test",
44 | devDependencies: {
45 | "@babel/preset-react": "*"
46 | }
47 | })
48 | await writeFile(path.join(projectDir, "src/renderer/file.jsx"), `
49 |
50 | Click Me
51 |
52 | `)
53 | await writeFile(path.join(projectDir, "src/renderer/index.js"), 'import "./file.jsx"')
54 | const configuration = require("electron-webpack/webpack.renderer.config.js")({production: true, configuration: {
55 | projectDir,
56 | }})
57 | await assertThat(configuration).throws(projectDir)
58 | })
59 |
--------------------------------------------------------------------------------
/packages/electron-webpack/src/config.ts:
--------------------------------------------------------------------------------
1 | import { readJson } from "fs-extra"
2 | import { Lazy } from "lazy-val"
3 | import * as path from "path"
4 | import { getConfig } from "read-config-file"
5 | import { ElectronWebpackConfiguration } from "./core"
6 | import { orNullIfFileNotExist } from "./util"
7 |
8 | export function getPackageMetadata(projectDir: string) {
9 | return new Lazy(() => orNullIfFileNotExist(readJson(path.join(projectDir, "package.json"))))
10 | }
11 |
12 | export interface ConfigurationRequest {
13 | projectDir: string
14 | packageMetadata: Lazy<{ [key: string]: any } | null> | null
15 | }
16 |
17 | export function getDefaultRelativeSystemDependentCommonSource(): string {
18 | return path.join("src", "common")
19 | }
20 |
21 | /**
22 | * Return configuration with resolved staticSourceDirectory / commonDistDirectory / commonSourceDirectory.
23 | */
24 | export async function getElectronWebpackConfiguration(context: ConfigurationRequest): Promise {
25 | const result = await getConfig({
26 | packageKey: "electronWebpack",
27 | configFilename: "electron-webpack",
28 | projectDir: context.projectDir,
29 | packageMetadata: context.packageMetadata
30 | })
31 | const configuration: ElectronWebpackConfiguration = result == null || result.result == null ? {} : result.result
32 | if (configuration.staticSourceDirectory == null) {
33 | configuration.staticSourceDirectory = "static"
34 | }
35 | if (configuration.commonDistDirectory == null) {
36 | configuration.commonDistDirectory = "dist"
37 | }
38 | if (configuration.commonSourceDirectory == null) {
39 | configuration.commonSourceDirectory = getDefaultRelativeSystemDependentCommonSource()
40 | }
41 | configuration.commonDistDirectory = path.resolve(context.projectDir, configuration.commonDistDirectory)
42 | configuration.commonSourceDirectory = path.resolve(context.projectDir, configuration.commonSourceDirectory)
43 |
44 | if (configuration.renderer === undefined) {
45 | configuration.renderer = {}
46 | }
47 | if (configuration.main === undefined) {
48 | configuration.main = {}
49 | }
50 |
51 | if (configuration.projectDir == null) {
52 | configuration.projectDir = context.projectDir
53 | }
54 | return configuration
55 | }
56 |
--------------------------------------------------------------------------------
/packages/electron-webpack/src/configurators/js.ts:
--------------------------------------------------------------------------------
1 | import { gte } from "semver"
2 | import { WebpackConfigurator } from "../main"
3 |
4 | export function createBabelLoader(configurator: WebpackConfigurator) {
5 | // better to use require instead of just preset name to avoid babel resolving (in our test we set custom resolver - and only in case of explicit required it works)
6 | const presets = [
7 | [
8 | require("@babel/preset-env").default, {
9 | modules: false,
10 | targets: computeBabelEnvTarget(configurator.isRenderer, configurator.electronVersion),
11 | }],
12 | ]
13 | const plugins = [
14 | require("@babel/plugin-syntax-dynamic-import").default,
15 | ]
16 |
17 | if (configurator.type !== "main" && configurator.hasDependency("element-ui")) {
18 | plugins.push([require("babel-plugin-component"), {
19 | libraryName: "element-ui",
20 | styleLibraryName: "theme-chalk"
21 | }])
22 | }
23 |
24 | addBabelItem(presets, configurator.getMatchingDevDependencies({
25 | includes: ["babel-preset-", "@babel/preset-"],
26 | excludes: ["babel-preset-env", "@babel/preset-env"],
27 | }))
28 | addBabelItem(plugins, configurator.getMatchingDevDependencies({
29 | includes: ["babel-plugin-", "@babel/plugin-"],
30 | excludes: ["babel-plugin-syntax-dynamic-import", "@babel/plugin-syntax-dynamic-import"],
31 | }))
32 |
33 | return {
34 | loader: "babel-loader",
35 | options: {
36 | presets,
37 | plugins
38 | }
39 | }
40 | }
41 |
42 | function addBabelItem(to: Array, names: Array) {
43 | for (const name of names) {
44 | const module = require(name)
45 | to.push([module.default || module])
46 | }
47 | }
48 |
49 | function computeBabelEnvTarget(isRenderer: boolean, electronVersion: string) {
50 | if (isRenderer) {
51 | return {
52 | electron: electronVersion
53 | }
54 | }
55 |
56 | let nodeVersion = "7.4.0"
57 | if (gte(electronVersion, "3.0.0")) {
58 | nodeVersion = "10.2.0"
59 | }
60 | else if (gte(electronVersion, "2.0.0")) {
61 | nodeVersion = "8.9.3"
62 | }
63 | else if (gte(electronVersion, "1.8.2")) {
64 | nodeVersion = "8.2.1"
65 | }
66 | else if (gte(electronVersion, "1.7.3")) {
67 | nodeVersion = "7.9.0"
68 | }
69 |
70 | return {
71 | node: nodeVersion
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/docs/en/dependency-management.md:
--------------------------------------------------------------------------------
1 | When creating `electron` applications, dependency management can be a little different for specific cases. To make sure everybody is on the same page, make sure to take a look at the below documentation.
2 |
3 | ### Using `yarn`
4 |
5 | As previously mentioned, the use of the [`yarn`](https://yarnpkg.com) package manager is **strongly** recommended, as opposed to `npm`. Aside from a more stable dependency tree, one other major benefit to using `yarn` is the ability to [*clean*](https://yarnpkg.com/en/docs/cli/clean) your `node_modules` to help eliminate redudant files that will help reduce your application's final build size.
6 |
7 | ### package.json
8 | #### `dependencies`
9 |
10 | These dependencies **will** be included in your final production application. If your application needs a certain module to function, then install it here!
11 |
12 | #### `devDependencies`
13 | These dependecies **will not** be included in your final production application. Here you can install modules needed specifically for development, like build scripts, task runners, `webpack` accessories, etc.
14 |
15 | ### Installing Native Node Modules
16 |
17 | When using native node modules, those written in C/C++, we need to ensure they are built against `electron`'s packaged `node` version. We can use [`electron-builder`](https://www.electron.build/cli)'s `install-app-deps` command to rebuild those modules to resolve any conflicts we might run into.
18 |
19 | ##### Running `install-app-deps`
20 | ```bash
21 | yarn add electron-builder --dev
22 | ./node_modules/.bin/electron-builder install-app-deps
23 | ```
24 |
25 | It may also be worth adding a `package.json` script for this command if you plan on using it often (`yarn rebuild-deps`).
26 |
27 | ```json tab="package.json"
28 | {
29 | "scripts": {
30 | "rebuild-deps": "electron-builder install-app-deps"
31 | }
32 | }
33 | ```
34 |
35 | If you choose not to use `electron-builder` as your build tool, you can still run the command using `npx` without side affects.
36 |
37 | ```bash
38 | # using `npm@^5.2.0`
39 | npx electron-builder install-app-deps
40 | ```
41 |
42 | #### Final Notes
43 | If you do expect your application to use native node modules, it is **highly recommended** to use [`electron-builder`](https://github.com/electron-userland/electron-builder) for your build tool as it handles these conflicts for you during the build step.
44 |
--------------------------------------------------------------------------------
/docs/en/environment-variables.md:
--------------------------------------------------------------------------------
1 | Sometimes you need your `webpack` build to be dependent upon specific environment variables. Whether you need to set a global API Url or even the application name, here's how you can set variables that `electron-webpack` will provide for your application.
2 |
3 | ### `ELECTRON_WEBPACK_APP_*`
4 |
5 | If you want `electron-webpack` to provide environment variables during the build process, use the `ELECTRON_WEBPACK_APP_*` namespace and they will be injected.
6 |
7 | #### Use Case
8 |
9 | Let's say our application uses a CI server to run tests, and we also want that build to use our development API. We can set an environment variable to define the base Url that [`axios`](https://www.npmjs.com/package/axios) can use.
10 |
11 | ##### Set our environment variable
12 | ```bash
13 | # linux and darwin systems
14 | ELECTRON_WEBPACK_APP_API_URL="http://dev.domain.com/api"
15 |
16 | # win32 systems
17 | set ELECTRON_WEBPACK_APP_API_URL="http://dev.domain.com/api"
18 | ```
19 |
20 | ##### Reference that variable in our code
21 | ```js
22 | import axios from 'axios'
23 |
24 | axios.defaults.baseURL = process.env.ELECTRON_WEBPACK_APP_API_URL
25 | ```
26 |
27 | ### `ELECTRON_ARGS`
28 |
29 | If you need to provide arguments to the `electron` process itself during development, you can do so using the `ELECTRON_ARGS` environment variable.
30 |
31 |
32 | ##### From the CLI
33 |
34 | When specifying `ELECTRON_ARGS` from the CLI, please note that it will be parsed as a JSON string of an array of values. Thus, you will have to wrap the value in square brackets, and on unix systems escape characters like double quotes.
35 | For example, if in package.json you have a script `"dev": "electron-webpack dev"`, you can invoke it with the `--inspect-brk` argument like this:
36 |
37 | ```
38 | # linux and darwin systems, git bash on windows
39 | ELECTRON_ARGS=[\"--inspect-brk=9229\"] yarn dev
40 |
41 | # win32 systems, cmd
42 | set ELECTRON_ARGS=["--inspect-brk=9229"]&&yarn dev
43 | ```
44 |
45 | ##### From an `.env` file
46 |
47 | Since `electron-webpack` uses the [dotenv](https://www.npmjs.com/package/dotenv) library under the hood, instead of wrestling with CLI arguments, escaping and differences between Windows and Unix systems, you can instead create an `.env` file and specify environment variables:
48 |
49 | ```bash tab=".env.development"
50 | ELECTRON_ARGS=["--inspect-brk=9229"]
51 | ```
52 |
--------------------------------------------------------------------------------
/docs/en/building.md:
--------------------------------------------------------------------------------
1 | # Building
2 |
3 | One important thing to understand is that `electron-webpack` is agnostic about the build tool you decide to use for creating your distributable `electron` application. The only concern of this module is to create a `webpack` output that functions properly and is optimized for the `electron` environment.
4 |
5 | ### Compiling your code
6 |
7 | Once you have `electron-webpack` configured for your project, you can simply run the `electron-webpack` command to compile your source code. To make things easier, make use of setting up a script in your `package.json`.
8 |
9 | ```json tab="package.json"
10 | {
11 | "scripts": {
12 | "compile": "electron-webpack"
13 | }
14 | }
15 | ```
16 |
17 | After running the above script with `yarn compile`, you now have a `webpack` output located in the `dist/` directory.
18 |
19 | ### Building a distribution
20 |
21 | Now that `electron-webpack` has created your `webpack` bundle, you can simply implement any build tool you would like. One thing to note is that additional optimizations have been made to work with [`electron-builder`](https://github.com/electron-userland/electron-builder). This build tool is perfect for any sized application, providing many features from creating installable executables to providing "auto update" support. `electron-webpack` also interally provides a base configuration for [`electron-builder`](https://github.com/electron-userland/electron-builder).
22 |
23 | #### Using `electron-builder`
24 |
25 | ```bash
26 | yarn add electron-builder --dev
27 | ```
28 |
29 | ##### Add additional `package.json` scripts
30 | ```json
31 | {
32 | "scripts": {
33 | /* compile source code and create webpack output */
34 | "compile": "electron-webpack",
35 |
36 | /* `yarn compile` & create distribution */
37 | "dist": "yarn compile && electron-builder",
38 |
39 | /* `yarn dist` & create unpacked distribution */
40 | "dist:dir": "yarn dist -- --dir -c.compression=store -c.mac.identity=null"
41 | }
42 | }
43 | ```
44 |
45 | Further configurations can be made in accordance to [`electron-builder`'s documentation](https://www.electron.build/).
46 |
47 | ### Final Notes
48 |
49 | When configuring your build tool of choice, just be sure to point to the `dist/` directory where your compiled application lives (this is already the default of `electron-builder`). Any questions or issues that may arise during the build step should be directed towards the respected build tool, and not `electron-webpack`.
50 |
--------------------------------------------------------------------------------
/docs/en/technical-differences-of-electron-compile.md:
--------------------------------------------------------------------------------
1 | # Technical Differences of `electron-compile`
2 | > Electron supporting package to compile JS and CSS in Electron applications
3 |
4 | `electron-compile` is a great package that offers a *zero configuration* setup without a predefine project structure. It surely has its place in the community, but just isn't comparable to `webpack` in terms of flexibility, development experience, and community extensions. Below are a few topics on why `electron-webpack` can be considerably better.
5 |
6 | ### Hot Module Replacement
7 | `electron-webpack` provides HMR for virtually all code in both `main` and `renderer` processes. `electron-compile` is currently limited to [live reloading](https://github.com/electron/electron-compile#live-reload--hot-module-reloading), which is enough for certain enviroments, but works for only a handful of file types. If you've worked with HMR before, then you definitly know it is something you can not live without afterwards.
8 |
9 | ### Runtime Dependencies
10 | Since `electron-compile` "compiles JS and CSS on the fly", it currently adds extra dependencies into the scope of your project. Sure, a small handful of modules isn't a big deal, but keeping production size down in the end is **always** important when distributing `electron` applications.
11 |
12 | ### Faster Build Times
13 | Internally, `electron-webpack` takes advantage of [`happypack`](https://github.com/amireh/happypack) to create multiple worker *threads*. Combined with Dll support, build times can be [significantly faster](https://github.com/amireh/happypack#benchmarks). It even has some additional optimizations for TypeScript users. And let's be honest, especially for large-scale applications, who doesn't want faster build times?
14 |
15 | ### Webpack Community
16 | When using `electron-compile` you are limited to a specific set of features. Sure you have most of the important ones covered, you just don't get an amazing and large community that `webpack` can provide. There are countless loaders and plugins to cover just about everything you may ever need. Not only is the community constantly implementing new tools, `electron` is explicitly support by the `webpack` team.
17 |
18 | In the end, `electron-compile` is still a fantastic tool when you need to quickly prototype a project together. It can even be enough for smaller scale applications that may not need all the fancy bells and whistles. But for those accustomed to HMR or developing larger scale applications, `electron-webpack` has you covered.
19 |
--------------------------------------------------------------------------------
/docs/en/dll-bundle-splitting.md:
--------------------------------------------------------------------------------
1 | One great feature of using `webpack` is being able to take advantage of the `webpack.DllPlugin` plugin. If you are unfamiliar with the `webpack.DllPlugin` plugin, make sure to read its [documentation](https://webpack.js.org/plugins/dll-plugin/) to better understand what problem it attempts to solve. In short, this can be used to [drastically improve](https://robertknight.github.io/posts/webpack-dll-plugins/) build time performance.
2 |
3 | Through custom configurations `electron-webpack` provides a place to define specific packages you would like to create a separate bundle for. You can treat this feature as a way to bundle all of your *vendor* libraries together.
4 |
5 | Once you have defined your modules, you can simply run `electron-webpack dll` to produce a `vendor` bundle that will be referenced in your final compilation. Let's go through that step-by-step.
6 |
7 | ### Define your modules
8 | Here we are defining our modules in `package.json`. If you prefer, you can also create a separate file for your configurations ([more info](./configuration.md)).
9 | ```json
10 | {
11 | "electronWebpack": {
12 | "renderer": {
13 | "dll": [
14 | "fooModule",
15 | "barModule"
16 | ]
17 | }
18 | }
19 | }
20 | ```
21 |
22 | ### Create the `vendor` bundle
23 | Once you have choosen your selected modules, you can run the below command to create your `vendor` bundle.
24 |
25 | ```bash
26 | electron-webpack dll
27 | ```
28 |
29 | It may be beneficial to create a `package.json` script for this command if you plan to use it often.
30 |
31 | ```json
32 | {
33 | "scripts": {
34 | "compile:dll": "electron-webpack dll"
35 | }
36 | }
37 | ```
38 |
39 | ## Final Notes
40 | Now that your `vendor` bundle is created, you can move forward with the standard `electron-webpack` compilation process.
41 |
42 | Please know that the `vendor` bundle is only created when you explicity run `electron-webpack dll`. For most cases, you may only need to run this one time. But as you add/remove modules or even update those modules, you will need to re-run the command to create an updated bundle.
43 |
44 | #### Using a `postinstall` hook
45 | One common practice when using Dll bundle splitting to define a `postinstall` command to create the `vendor` bundle whenever new dependecies are installed. This can help ensure everything is up to date so you don't have to manually run this yourself.
46 |
47 | ```json tab="package.json"
48 | {
49 | "scripts": {
50 | "postinstall": "electron-webpack dll"
51 | }
52 | }
53 | ```
54 |
--------------------------------------------------------------------------------
/packages/electron-webpack/src/configurators/ts.ts:
--------------------------------------------------------------------------------
1 | import * as path from "path"
2 | import { WebpackConfigurator } from "../main"
3 | import { getFirstExistingFile } from "../util"
4 |
5 | export async function configureTypescript(configurator: WebpackConfigurator) {
6 | const hasTsChecker = configurator.hasDevDependency("fork-ts-checker-webpack-plugin") || configurator.hasDevDependency("electron-webpack-ts")
7 | if (!(hasTsChecker || configurator.hasDevDependency("ts-loader"))) {
8 | return
9 | }
10 |
11 | // add after js
12 | configurator.extensions.splice(1, 0, ".ts", ".tsx")
13 |
14 | const isTranspileOnly = configurator.isTest || (hasTsChecker && !configurator.isProduction)
15 |
16 | const tsConfigFile = await getFirstExistingFile([path.join(configurator.sourceDir, "tsconfig.json"), path.join(configurator.projectDir, "tsconfig.json")], null)
17 | // check to produce clear error message if no tsconfig.json
18 | if (tsConfigFile == null) {
19 | throw new Error(`Please create tsconfig.json in the "${configurator.projectDir}":\n\n{\n "extends": "./node_modules/electron-webpack/tsconfig-base.json"\n}\n\n`)
20 | }
21 |
22 | if (configurator.debug.enabled) {
23 | configurator.debug(`Using ${tsConfigFile}`)
24 | }
25 |
26 | // no sense to use fork-ts-checker-webpack-plugin for production build
27 | if (isTranspileOnly && !configurator.isTest) {
28 | const hasVue = configurator.hasDependency("vue")
29 | const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin")
30 | configurator.plugins.push(new ForkTsCheckerWebpackPlugin({
31 | tsconfig: tsConfigFile,
32 | logger: configurator.env.forkTsCheckerLogger || {
33 | info: () => {
34 | // ignore
35 | },
36 |
37 | warn: console.warn.bind(console),
38 | error: console.error.bind(console),
39 | },
40 | vue: hasVue
41 | }))
42 | }
43 |
44 | const tsLoaderOptions: any = {
45 | // use transpileOnly mode to speed-up compilation
46 | // in the test mode also, because checked during dev or production build
47 | transpileOnly: isTranspileOnly,
48 | appendTsSuffixTo: [/\.vue$/],
49 | configFile: tsConfigFile,
50 | }
51 |
52 | if (configurator.debug.enabled) {
53 | configurator.debug(`ts-loader options: ${JSON.stringify(tsLoaderOptions, null, 2)}`)
54 | }
55 |
56 | configurator.rules.push({
57 | test: /\.tsx?$/,
58 | exclude: /node_modules/,
59 | use: [
60 | {
61 | loader: "ts-loader",
62 | options: tsLoaderOptions
63 | },
64 | ],
65 | })
66 | }
--------------------------------------------------------------------------------
/docs/en/project-structure.md:
--------------------------------------------------------------------------------
1 | Everybody likes to structure their projects differently, and `electron-webpack` is aware of that. For the most part, the only configurations needed are [entry points](https://webpack.js.org/concepts/entry-points/) for `webpack` to step in.
2 |
3 | If you are looking to follow common electron practices or wanting an example template, make sure to check out [`electron-webpack-quick-start`](https://github.com/electron-userland/electron-webpack-quick-start). It has the bare minimun setup, but can be easily modified to meet many needs. The use of this template, of course, is optional.
4 |
5 | ### Default Structure
6 | Below is the default file tree that `electron-webpack` expects without any custom configuration. Please know that source directories can be adjusted to meet your needs ([more info](./configuration.md#source-directories)).
7 |
8 | ```
9 | my-project/
10 | ├─ src/
11 | │ ├─ main/
12 | │ │ └─ index.js
13 | │ ├─ renderer/
14 | │ │ └─ index.js
15 | │ └─ common/
16 | └─ static/
17 | ```
18 |
19 | ##### Main Process (`src/main/`)
20 | Here you can add all of your `main` process code.
21 |
22 | ##### Renderer Process (`src/renderer/`) [optional]
23 | Here you can add all of your `renderer` process code. Bundling of the `renderer` process is optional for cases where you may want to use an external tool such as [`electron-next`](https://github.com/leo/electron-next). Notice that there isn't a entry `index.html`, that's because it is created for you ([more info](./development.md#use-of-html-webpack-plugin)).
24 |
25 | ##### Common Scripts (`src/common/`) [optional]
26 | This is a convenient place where you can place utility type files that you expect to use between both processes. Thanks to `webpack` aliasing, you can easily import files from here using the `common` alias.
27 |
28 | ##### Static Assets (`static/`) [optional]
29 | There are some instances were we may not want `webpack` to bundle particular assets, like those being consumed by modules like `fs`. Here is where we can put them and then reliably access them in both development and production ([more info](./using-static-assets.md)).
30 |
31 | ### `webpack` entry points
32 | One important thing to notice is that `electron-webpack` expects that your `main` process and `renderer` process source code is separated using different directories.
33 | Within each process's directory, one of the following entry files is expected...
34 |
35 | * `index.js` / `main.js`
36 | * `index.ts` / `main.ts` (when using the [TypeScript](./add-ons.md#typescript) add-on)
37 |
38 | The [entry files](https://webpack.js.org/concepts/entry-points/) are the main starting point of your `webpack` bundle, so everything found within its dependency tree will be bundled.
39 |
--------------------------------------------------------------------------------
/test/src/test.ts:
--------------------------------------------------------------------------------
1 | import { randomBytes } from "crypto"
2 | import { mkdir, move, outputJson, writeFile } from "fs-extra"
3 | import * as path from "path"
4 | import { doTest, getMutableProjectDir, rootDir, testWebpack, tmpDir } from "./helpers/helper"
5 |
6 | afterEach(() => tmpDir.cleanup())
7 |
8 | test("app", () => doTest("webpack.app.config.js"))
9 |
10 | test("main production", () => doTest("webpack.main.config.js"))
11 |
12 | test("renderer custom sourceDirectory", async () => {
13 | const projectDir = await getMutableProjectDir()
14 | await move(path.join(projectDir, "src/renderer"), path.join(projectDir, "customRenderer"))
15 | const configuration = await require("electron-webpack/webpack.renderer.config.js")({production: true, configuration: {
16 | projectDir,
17 | renderer: {
18 | sourceDirectory: "customRenderer"
19 | },
20 | }})
21 | await testWebpack(configuration, projectDir)
22 | })
23 |
24 | test("main extra entry point and custom source dir", async () => {
25 | const projectDir = await getMutableProjectDir()
26 | await move(path.join(projectDir, "src/main"), path.join(projectDir, "customMain"))
27 | const configuration = await require("electron-webpack/webpack.main.config.js")({production: true, configuration: {
28 | projectDir,
29 | main: {
30 | extraEntries: ["@/foo.js"],
31 | sourceDirectory: "customMain"
32 | },
33 | }})
34 | await testWebpack(configuration, projectDir)
35 | })
36 |
37 | test("renderer production", async () => {
38 | const projectDir = await getMutableProjectDir()
39 |
40 | function createTestAsset(dirName: string, fileExt = "png") {
41 | const dir = path.join(projectDir, dirName)
42 | return mkdir(dir).then(() => writeFile(path.join(dir, `foo.${fileExt}`), randomBytes(100 * 1024)))
43 | }
44 |
45 | // size of file must be greater than url-loader limit
46 | await Promise.all([
47 | createTestAsset("a"),
48 | createTestAsset("b"),
49 | createTestAsset("static", "txt"),
50 | ])
51 |
52 | const configuration = await require("electron-webpack/webpack.renderer.config.js")({configuration: {projectDir}, production: true})
53 | await testWebpack(configuration, projectDir)
54 | })
55 |
56 | test("typescript", async () => {
57 | const projectDir = await getMutableProjectDir("typescript")
58 | // noinspection ReservedWordAsName
59 | await outputJson(path.join(projectDir, "tsconfig.json"), {
60 | extends: rootDir.replace(/\\/g, "/") + "/packages/electron-webpack/tsconfig-base.json",
61 | compilerOptions: {
62 | baseUrl: "src",
63 | },
64 | })
65 | const configuration = await require("electron-webpack/webpack.main.config.js")({production: true, configuration: {
66 | projectDir,
67 | }})
68 | await testWebpack(configuration, projectDir)
69 | })
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron-webpack",
3 | "private": true,
4 | "license": "MIT",
5 | "author": "Vladimir Krivosheev ",
6 | "scripts": {
7 | "compile": "cross-env ts-babel packages/electron-webpack test && yarn schema",
8 | "lint": "tslint -c ./node_modules/electron-builder-tslint-config/tslint.json -p packages/electron-webpack --exclude \"**/*.js\"",
9 | "release": "BABEL_ENV=production yarn compile && ./npm-publish.sh && conventional-changelog -p angular -i CHANGELOG.md -s",
10 | "test": "yarn compile && yarn lint && jest",
11 | "serve-docs": "mkdocs serve",
12 | "deploy-docs": "mkdocs build --clean && netlifyctl deploy --publish-directory site",
13 | "schema": "ts-json-schema-generator --path \"packages/electron-webpack/src/**/*.ts\" --no-top-ref --no-type-check --type ElectronWebpackConfiguration --expose export > packages/electron-webpack/scheme.json",
14 | "//": "update-deps task intended only for core maintainers",
15 | "update-deps": "npm-check-updates -u && npx lerna exec -- npm-check-updates -u"
16 | },
17 | "devDependencies": {
18 | "@types/bluebird": "^3.5.30",
19 | "@types/electron-devtools-installer": "^2.2.0",
20 | "@types/fs-extra": "^8.1.0",
21 | "@types/jest": "^25.2.1",
22 | "@types/memory-fs": "^0.3.2",
23 | "@types/node": "^13.11.0",
24 | "@types/semver": "^7.1.0",
25 | "@types/webpack": "^4.41.10",
26 | "@types/webpack-merge": "^4.1.5",
27 | "babel-preset-jest": "^25.2.6",
28 | "babel-preset-ts-node8": "^4.0.3",
29 | "cross-env": "^7.0.2",
30 | "electron-builder-tslint-config": "^1.1.0",
31 | "fs-extra": "^9.0.0",
32 | "jest-cli": "^25.2.7",
33 | "jest-junit": "^10.0.0",
34 | "memory-fs": "^0.5.0",
35 | "temp-file": "^3.3.7",
36 | "terser-webpack-plugin": "^2.3.5",
37 | "ts-babel": "^6.1.7",
38 | "ts-json-schema-generator": "^0.65.0",
39 | "tslint": "^6.1.1",
40 | "typescript": "^3.8.3",
41 | "webpack": "^4.42.1"
42 | },
43 | "workspaces": [
44 | "packages/*"
45 | ],
46 | "resolutions": {
47 | "js-yaml": "^3.13.1"
48 | },
49 | "jest": {
50 | "globals": {
51 | "ts-jest": {
52 | "tsConfigFile": "test/tsconfig.json"
53 | }
54 | },
55 | "transform": {
56 | "^.+\\.js$": "/test/babel-jest.js"
57 | },
58 | "moduleFileExtensions": [
59 | "ts",
60 | "tsx",
61 | "js",
62 | "json"
63 | ],
64 | "testRegex": "(/test/out/.*|\\.(test|spec))\\.(ts|tsx|js)$",
65 | "testEnvironment": "node",
66 | "testPathIgnorePatterns": [
67 | "[\\/]{1}helpers[\\/]{1}"
68 | ],
69 | "roots": [
70 | "test/out"
71 | ],
72 | "modulePaths": [
73 | "/packages/electron-webpack/node_modules",
74 | "/packages"
75 | ]
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/packages/electron-webpack/src/electron-main-hmr/HmrClient.ts:
--------------------------------------------------------------------------------
1 | import Crocket from "crocket"
2 |
3 | const debug = require("debug")("electron-webpack:main-client-hmr")
4 |
5 | interface BuiltMessage {
6 | hash: string
7 | }
8 |
9 | export class HmrClient {
10 | lastHash: string | null = null
11 |
12 | private readonly ipc = new Crocket()
13 |
14 | constructor(socketPath: string, private hot: __WebpackModuleApi.Hot, private readonly currentHashGetter: () => string) {
15 | if (hot == null) {
16 | throw new Error(`[HMR] Hot Module Replacement is disabled.`)
17 | }
18 |
19 | this.ipc.connect({path: socketPath}, error => {
20 | if (error != null) {
21 | console.error(error.stack || error.toString())
22 | }
23 | if (debug.enabled) {
24 | debug(`Connected to server (${socketPath})`)
25 | }
26 | })
27 |
28 | this.ipc.on("error", error => {
29 | console.error(error.stack || error.toString())
30 | })
31 |
32 | this.ipc.on("/built", data => {
33 | this.lastHash = data.hash
34 | if (this.isUpToDate()) {
35 | if (debug.enabled) {
36 | debug(`Up to date, hash ${data.hash}`)
37 | }
38 | return
39 | }
40 |
41 | const status = hot.status()
42 | if (status === "idle") {
43 | this.check()
44 | }
45 | else if (status === "abort" || status === "fail") {
46 | console.warn(`[HMR] Cannot apply update as a previous update ${status}ed. Need to do a full reload!`)
47 | }
48 | else if (debug.enabled) {
49 | debug(`Cannot check changes, status ${status}`)
50 | }
51 | })
52 | }
53 |
54 | private isUpToDate() {
55 | return this.lastHash === this.currentHashGetter()
56 | }
57 |
58 | private check() {
59 | this.hot.check(true)
60 | .then(outdatedModules => {
61 | if (outdatedModules == null) {
62 | console.warn(`[HMR] Cannot find update. Need to do a full reload!`)
63 | console.warn(`[HMR] (Probably because of restarting the webpack-dev-server)`)
64 | return
65 | }
66 |
67 | require("webpack/hot/log-apply-result")(outdatedModules, outdatedModules)
68 |
69 | if (this.isUpToDate()) {
70 | console.log(`[HMR] App is up to date.`)
71 | }
72 | })
73 | .catch(error => {
74 | const status = this.hot.status()
75 | if (status === "abort" || status === "fail") {
76 | console.warn(`[HMR] ${error.stack || error.toString()}`)
77 | console.warn("[HMR] Cannot apply update. Need to do a full reload - application will be restarted")
78 | require("electron").app.exit(100)
79 | }
80 | else {
81 | console.warn(`[HMR] Update failed: ${error.stack || error.message}`)
82 | }
83 | })
84 | }
85 | }
--------------------------------------------------------------------------------
/packages/electron-webpack/src/dev/devUtil.ts:
--------------------------------------------------------------------------------
1 | import chalk from "chalk"
2 | import { ChildProcess } from "child_process"
3 |
4 | export interface LineFilter {
5 | filter(line: string): boolean
6 | }
7 |
8 | function filterText(s: string, lineFilter: LineFilter | null) {
9 | const lines = s
10 | .trim()
11 | .split(/\r?\n/)
12 | .filter(it => {
13 | if (lineFilter != null && !lineFilter.filter(it)) {
14 | return false
15 | }
16 |
17 | // https://github.com/electron/electron/issues/4420
18 | // this warning can be safely ignored
19 | if (it.includes("Couldn't set selectedTextBackgroundColor from default ()")) {
20 | return false
21 | }
22 | if (it.includes("Use NSWindow's -titlebarAppearsTransparent=YES instead.")) {
23 | return false
24 | }
25 | return !it.includes("Warning: This is an experimental feature and could change at any time.")
26 | && !it.includes("No type errors found")
27 | && !it.includes("webpack: Compiled successfully.")
28 | })
29 |
30 | if (lines.length === 0) {
31 | return null
32 | }
33 | return " " + lines.join(`\n `) + "\n"
34 | }
35 |
36 | export function logProcessErrorOutput(label: "Electron" | "Renderer" | "Main", childProcess: ChildProcess) {
37 | childProcess.stderr!!.on("data", data => {
38 | logProcess(label, data.toString(), chalk.red)
39 | })
40 | }
41 |
42 | export function logError(label: "Electron" | "Renderer" | "Main", error: Error) {
43 | logProcess(label, error.stack || error.toString(), chalk.red)
44 | }
45 |
46 | const LABEL_LENGTH = 28
47 |
48 | export function logProcess(label: "Electron" | "Renderer" | "Main", data: string | Buffer, labelColor: any, lineFilter: LineFilter | null = null) {
49 | const log = filterText(data.toString(), lineFilter)
50 | if (log == null || log.length === 0) {
51 | return
52 | }
53 |
54 | process.stdout.write(
55 | labelColor.bold(`┏ ${label} ${"-".repeat(LABEL_LENGTH - label.length - 1)}`) +
56 | "\n\n" + log + "\n" +
57 | labelColor.bold(`┗ ${"-".repeat(LABEL_LENGTH)}`) +
58 | "\n"
59 | )
60 | }
61 |
62 | export class DelayedFunction {
63 | private readonly executor: () => void
64 | private handle: NodeJS.Timer | null = null
65 |
66 | constructor(executor: () => void) {
67 | this.executor = () => {
68 | this.handle = null
69 | executor()
70 | }
71 | }
72 |
73 | schedule() {
74 | this.cancel()
75 | this.handle = setTimeout(this.executor, 5000)
76 | }
77 |
78 | cancel() {
79 | const handle = this.handle
80 | if (handle != null) {
81 | this.handle = null
82 | clearTimeout(handle)
83 | }
84 | }
85 | }
86 |
87 | export function getCommonEnv() {
88 | return {
89 | ...process.env,
90 | NODE_ENV: "development",
91 | // to force debug colors in the child process
92 | DEBUG_COLORS: true,
93 | DEBUG_FD: "1",
94 | }
95 | }
--------------------------------------------------------------------------------
/packages/electron-webpack/src/dev/ChildProcessManager.ts:
--------------------------------------------------------------------------------
1 | import { ChildProcess, spawn, SpawnOptions } from "child_process"
2 | import * as path from "path"
3 |
4 | const debug = require("debug")("electron-webpack")
5 |
6 | export function run(program: string, args: Array, options: SpawnOptions) {
7 | const isWin = process.platform === "win32"
8 | return spawn(isWin ? path.join(__dirname, "../../vendor/runnerw.exe") : program, isWin ? [program].concat(args) : args, options)
9 | }
10 |
11 | export class ChildProcessManager {
12 | // noinspection TypeScriptFieldCanBeMadeReadonly
13 | private mainProcessExitCleanupCallback: (() => void) | null = null
14 | // noinspection TypeScriptFieldCanBeMadeReadonly
15 | private child: ChildProcess | null
16 |
17 | constructor(child: ChildProcess, debugLabel: string, promiseNotifier: PromiseNotifier | null) {
18 | this.child = child
19 |
20 | require("async-exit-hook")((callback: () => void) => {
21 | this.mainProcessExitCleanupCallback = callback
22 | const child = this.child
23 | if (child == null) {
24 | return
25 | }
26 |
27 | this.child = null
28 |
29 | if (promiseNotifier != null) {
30 | promiseNotifier.resolve()
31 | }
32 |
33 | if (debug.enabled) {
34 | debug(`Send SIGINT to ${debugLabel}`)
35 | }
36 |
37 | if (process.platform === "win32") {
38 | child.stdin!!.end(Buffer.from([5, 5]))
39 | }
40 | else {
41 | child.kill("SIGINT")
42 | }
43 | })
44 |
45 | child.on("close", code => {
46 | const mainProcessExitCleanupCallback = this.mainProcessExitCleanupCallback
47 | if (mainProcessExitCleanupCallback != null) {
48 | this.mainProcessExitCleanupCallback = null
49 | mainProcessExitCleanupCallback()
50 | }
51 |
52 | const child = this.child
53 | if (child == null) {
54 | return
55 | }
56 |
57 | this.child = null
58 |
59 | const message = `${debugLabel} exited with code ${code}`
60 |
61 | if (promiseNotifier != null) {
62 | promiseNotifier.reject(new Error(message))
63 | }
64 |
65 | if (code === 0) {
66 | if (debug.enabled) {
67 | debug(message)
68 | // otherwise no newline in the terminal
69 | process.stderr.write("\n")
70 | }
71 | }
72 | else {
73 | process.stderr.write(`${message}\n`)
74 | }
75 | })
76 | }
77 | }
78 |
79 | export class PromiseNotifier {
80 | constructor(private _resolve: (() => void) | null, private _reject: ((error: Error) => void) | null) {
81 | }
82 |
83 | resolve() {
84 | const r = this._resolve
85 | if (r != null) {
86 | this._resolve = null
87 | r()
88 | }
89 | }
90 |
91 | reject(error: Error) {
92 | if (this._resolve != null) {
93 | this._resolve = null
94 | }
95 |
96 | const _reject = this._reject
97 | if (_reject != null) {
98 | this._reject = null
99 | _reject(error)
100 | }
101 | }
102 | }
--------------------------------------------------------------------------------
/packages/electron-webpack/src/plugins/WatchMatchPlugin.ts:
--------------------------------------------------------------------------------
1 | import { Compiler } from "webpack"
2 |
3 | export class WatchFilterPlugin {
4 | constructor(private readonly filter: WatchFileSystemFilter, private readonly debug: any) {
5 | }
6 |
7 | apply(compiler: Compiler) {
8 | compiler.hooks.afterEnvironment.tap("WatchFilterPlugin", () => {
9 | (compiler as any).watchFileSystem = new IgnoringWatchFileSystem((compiler as any).watchFileSystem, this.filter, this.debug)
10 | })
11 | }
12 | }
13 |
14 | interface WatchFileSystem {
15 | watch(files: Array, dirs: Array, missing: Array, startTime: number, options: any, callback: WatchFileSystemCallback, callbackUndelayed: () => void): void
16 | }
17 |
18 | // include or not
19 | export type WatchFileSystemFilter = (file: string) => boolean
20 |
21 | export type WatchFileSystemCallback = (error: Error | null, filesModified?: Array, dirsModified?: Array, missingModified?: Array, fileTimestamps?: { [key: string]: number }, dirTimestamps?: { [key: string]: number }) => void
22 |
23 | class IgnoringWatchFileSystem {
24 | constructor(private readonly wfs: WatchFileSystem, private readonly filter: WatchFileSystemFilter, private readonly debug: any) {
25 | }
26 |
27 | watch(files: Array, dirs: Array, missing: Array, startTime: number, options: any, callback: WatchFileSystemCallback, callbackUndelayed: () => void) {
28 | const includedFiles: Array = []
29 | const includedDirs: Array = []
30 | const excludedFiles: Array = []
31 | const excludedDirs: Array = []
32 | separate(this.filter, files, includedFiles, excludedFiles)
33 | separate(this.filter, dirs, includedDirs, excludedDirs)
34 |
35 | if (this.debug.enabled) {
36 | this.debug(`files:${stringifyList(files)}\ndirs:${stringifyList(dirs)}\nmissing:${stringifyList(missing)}`)
37 | this.debug(`includedFiles:${stringifyList(includedFiles)}\nincludedDirs:${stringifyList(includedDirs)}\nexcludedFiles:${stringifyList(excludedFiles)}\nexcludedDirs:${stringifyList(excludedDirs)}`)
38 | }
39 |
40 | return this.wfs.watch(includedFiles, includedDirs, missing, startTime, options, (error, filesModified, dirsModified, missingModified, fileTimestamps, dirTimestamps) => {
41 | if (error != null) {
42 | callback(error)
43 | return
44 | }
45 |
46 | for (const p of excludedFiles) {
47 | fileTimestamps![p] = 1
48 | }
49 |
50 | for (const p of excludedDirs) {
51 | dirTimestamps![p] = 1
52 | }
53 |
54 | callback(null, filesModified, dirsModified, missingModified, fileTimestamps, dirTimestamps)
55 | }, callbackUndelayed)
56 | }
57 | }
58 |
59 | function separate(filter: WatchFileSystemFilter, list: Array, included: Array, excluded: Array) {
60 | for (const file of list) {
61 | (filter(file) ? included : excluded).push(file)
62 | }
63 | }
64 |
65 | function stringifyList(list: Array) {
66 | return `\n ${list.map(it => it.startsWith(process.cwd()) ? it.substring(process.cwd().length + 1) : it).join(",\n ")}`
67 | }
--------------------------------------------------------------------------------
/docs/en/index.md:
--------------------------------------------------------------------------------
1 | # electron-webpack [](https://npmjs.org/package/electron-webpack)
2 |
3 | > Because setting up `webpack` in the `electron` environment shouldn't be difficult.
4 |
5 | ## Overview
6 | Modern web development practices today require a lot of setup with things like `webpack` to bundle your code, `babel` for transpiling, `eslint` for linting, and so much more that the list just goes on. Unfortunately when creating `electron` applications, all of that setup just became much more difficult. The primary aim of `electron-webpack` is to eliminate all preliminary setup with one simple install so you can get back to developing your application.
7 |
8 | > Why create a module and not a full boilerplate?
9 |
10 | If you've been in the JavaScript world for even a short period of time, you are very aware that things are always changing, and development setup is no exclusion. Putting all development scripts into a single **updatable** module just makes sense. Sure a full featured boilerplate works too, but doing also involves needing to manually update those pesky `webpack` configuration files that some may call *magic* when something new comes out.
11 |
12 | Here are some of the awesome features you'll find using `electron-webpack`:
13 |
14 | * Detailed [documentation](https://webpack.electron.build)
15 | * Use of [`webpack`](https://webpack.js.org/) for source code bundling
16 | * Use of [`webpack-dev-server`](https://github.com/webpack/webpack-dev-server) for development
17 | * HMR for both `renderer` and `main` processes
18 | * Use of [`@babel/preset-env`](https://github.com/babel/babel/tree/master/packages/babel-preset-env) that is automatically configured based on your `electron` version
19 | * Ability to add custom `webpack` loaders, plugins, etc.
20 | * [Add-ons](./add-ons.md) to support items like [TypeScript](http://www.typescriptlang.org/), [Less](http://lesscss.org/), [EJS](http://www.embeddedjs.com/), etc.
21 |
22 | ## Quick Start
23 | Get started fast with [electron-webpack-quick-start](https://github.com/electron-userland/electron-webpack-quick-start).
24 | ```bash
25 | # create a directory of your choice, and copy template using curl
26 | mkdir my-project && cd my-project
27 | curl -fsSL https://github.com/electron-userland/electron-webpack-quick-start/archive/master.tar.gz | tar -xz --strip-components 1
28 |
29 | # or copy template using git clone
30 | git clone https://github.com/electron-userland/electron-webpack-quick-start.git
31 | cd electron-webpack-quick-start
32 | rm -rf .git
33 |
34 | # install dependencies
35 | yarn
36 | ```
37 |
38 | If you already have an existing project, or are looking for a custom approach outside of the quick start template, make sure to read over the [Core Concepts](./core-concepts.md), [Project Structure](./project-structure.md), and [Development](./development.md) sections of `electron-webpack`'s documentation.
39 |
40 | ### Next Steps
41 | Make sure to take advantage of the detailed [documentation](https://webpack.electron.build) that `electron-webpack` provides. It covers everything from how things work internally, adding custom configurations, and building your application.
42 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
--------------------------------------------------------------------------------
/test/babel-jest.js:
--------------------------------------------------------------------------------
1 | "use strict"
2 |
3 | let babel
4 | const crypto = require("crypto")
5 | const fs = require("fs")
6 | const jestPreset = require("babel-preset-jest")
7 | const path = require("path")
8 |
9 | const convert = require("convert-source-map")
10 |
11 | let babelRc;
12 |
13 | function getBabelRcDigest() {
14 | if (babelRc == null) {
15 | let configFile
16 | try {
17 | configFile = fs.readFileSync(path.join(__dirname, ".babelrc"))
18 | }
19 | catch (e) {
20 | configFile = fs.readFileSync(path.join(__dirname, "..", ".babelrc"))
21 | }
22 |
23 | babelRc = crypto
24 | .createHash("md5")
25 | .update(configFile)
26 | .digest("base64")
27 | }
28 | return babelRc;
29 | }
30 |
31 | // compiled by ts-babel - do not transform
32 | function isFullyCompiled(fileData) {
33 | return fileData.startsWith(`"use strict";`) && fileData.includes("var _")
34 | }
35 |
36 | function createTransformer(options) {
37 | options = Object.assign({}, options, {
38 | presets: (options && options.presets || []).concat([jestPreset]),
39 | })
40 |
41 | delete options.cacheDirectory
42 |
43 | return {
44 | canInstrument: true,
45 | getCacheKey(fileData, filename, configString, _ref2) {
46 | return crypto.createHash("md5")
47 | .update(fileData)
48 | .update(isFullyCompiled(fileData) ? "f": "p")
49 | .update(configString)
50 | .update(getBabelRcDigest())
51 | .update(_ref2.instrument ? "instrument" : "")
52 | .digest("hex")
53 | },
54 | process(src, filename, config, transformOptions) {
55 | // allow ~/Documents/electron-builder/node_modules/electron-builder/out/targets/nsis.js:1
56 |
57 | const nodeModulesIndexOf = filename.indexOf("node_modules")
58 | if ((nodeModulesIndexOf > 0 && !filename.includes("electron-builder", nodeModulesIndexOf)) || !(filename.includes("/out/") || filename.includes("\\out\\"))) {
59 | // console.log(`Skip ${filename}`)
60 | return src
61 | }
62 |
63 | // console.log(`Do ${filename}`)
64 |
65 | if (babel == null) {
66 | babel = require('@babel/core')
67 | }
68 |
69 | if (isFullyCompiled(src)) {
70 | // console.log(`!canCompile!o ${filename}`)
71 | return src
72 | }
73 |
74 | let plugins = options.plugins || []
75 |
76 | const lastLine = src.lastIndexOf("\n") + 1
77 | if (lastLine > 0 && lastLine < src.length) {
78 | if (src.substring(lastLine).startsWith("//#")) {
79 | src = src.substring(0, lastLine - 1)
80 | }
81 | }
82 |
83 | const finalOptions = Object.assign({}, options, {
84 | filename,
85 | plugins,
86 | inputSourceMap: JSON.parse(fs.readFileSync(filename + ".map", "utf-8")),
87 | sourceMaps: "inline",
88 | })
89 | if (transformOptions && transformOptions.instrument) {
90 | finalOptions.auxiliaryCommentBefore = ' istanbul ignore next '
91 | finalOptions.plugins = plugins.concat(require('babel-plugin-istanbul').default);
92 | }
93 |
94 | const result = babel.transform(src, finalOptions)
95 | return result.code + "\n//# sourceMappingURL=data:application/json;base64," + convert.fromObject(result.map).toBase64()
96 | }
97 | }
98 | }
99 |
100 | module.exports = createTransformer()
101 | module.exports.createTransformer = createTransformer
--------------------------------------------------------------------------------
/packages/electron-webpack/src/plugins/WebpackRemoveOldAssetsPlugin.ts:
--------------------------------------------------------------------------------
1 | import * as bluebird from "bluebird"
2 | import { lstat, readdir, remove, Stats } from "fs-extra"
3 | import * as path from "path"
4 | import { Compiler } from "webpack"
5 | import { orNullIfFileNotExist } from "../util"
6 |
7 | export const MAX_FILE_REQUESTS = 8
8 | export const CONCURRENCY = {concurrency: MAX_FILE_REQUESTS}
9 |
10 | const debug = require("debug")("electron-webpack:clean")
11 |
12 | export async function walk(initialDirPath: string, filter?: Filter | null): Promise> {
13 | const result: Array = []
14 | const queue: Array = [initialDirPath]
15 | let addDirToResult = false
16 | while (queue.length > 0) {
17 | const dirPath = queue.pop()!
18 |
19 | const childNames = await orNullIfFileNotExist(readdir(dirPath))
20 | if (childNames == null) {
21 | continue
22 | }
23 |
24 | if (addDirToResult) {
25 | result.push(dirPath)
26 | }
27 | else {
28 | addDirToResult = true
29 | }
30 |
31 | childNames.sort()
32 |
33 | const dirs: Array = []
34 | // our handler is async, but we should add sorted files, so, we add file to result not in the mapper, but after map
35 | const sortedFilePaths = await bluebird.map(childNames, name => {
36 | const filePath = dirPath + path.sep + name
37 | return lstat(filePath)
38 | .then(stat => {
39 | if (filter != null && !filter(filePath, stat)) {
40 | return null
41 | }
42 |
43 | if (stat.isDirectory()) {
44 | dirs.push(name)
45 | return null
46 | }
47 | else {
48 | return filePath
49 | }
50 | })
51 | }, CONCURRENCY)
52 |
53 | for (const child of sortedFilePaths) {
54 | if (child != null) {
55 | result.push(child)
56 | }
57 | }
58 |
59 | dirs.sort()
60 | for (const child of dirs) {
61 | queue.push(dirPath + path.sep + child)
62 | }
63 | }
64 |
65 | return result
66 | }
67 |
68 | export type Filter = (file: string, stat: Stats) => boolean
69 |
70 | export class WebpackRemoveOldAssetsPlugin {
71 | constructor(private readonly dllManifest: string | null) {
72 | }
73 |
74 | apply(compiler: Compiler) {
75 | compiler.hooks.afterEmit.tapAsync("WebpackRemoveOldAssetsPlugin", (compilation: any, callback: (error?: Error) => void) => {
76 | const newlyCreatedAssets = compilation.assets
77 | const outDir = compiler.options.output!.path!
78 | walk(outDir, (file, stat) => {
79 | // dll plugin
80 | if (file === this.dllManifest) {
81 | return false
82 | }
83 |
84 | const relativePath = file.substring(outDir.length + 1)
85 | if (stat.isFile()) {
86 | return newlyCreatedAssets[relativePath] == null
87 | }
88 | else if (stat.isDirectory()) {
89 | for (const p of Object.keys(newlyCreatedAssets)) {
90 | if (p.length > relativePath.length && (p[relativePath.length] === "/" || p[relativePath.length] === "\\") && p.startsWith(relativePath)) {
91 | return false
92 | }
93 | }
94 | return true
95 | }
96 | return false
97 | })
98 | .then((it): any => {
99 | if (it.length === 0) {
100 | return null
101 | }
102 |
103 | if (debug.enabled) {
104 | debug(`Remove outdated files:\n ${it.join("\n ")}`)
105 | }
106 | return bluebird.map(it, it => remove(it), CONCURRENCY)
107 | })
108 | .then(() => callback())
109 | .catch(callback)
110 | })
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/docs/en/development.md:
--------------------------------------------------------------------------------
1 | # Development
2 |
3 | ### Installation
4 | Since `electron-webpack` is a module, not a boilerplate, let's install that and few other dependencies.
5 |
6 | ```bash
7 | yarn add electron-webpack electron webpack --dev
8 | yarn add source-map-support
9 | ```
10 |
11 | ### Starting in Development Mode
12 | By default, `electron-webpack` expects, at minimum, a `main` process entry point in `src/main/index.js` ([more info](./project-structure.md)). Once you have your entry files setup, you can run `electron-webpack dev` to get started. To make things easier, make use of setting up a script in your `package.json` to start your application in development mode.
13 |
14 | ```json tab="package.json"
15 | {
16 | "scripts": {
17 | "dev": "electron-webpack dev"
18 | }
19 | }
20 | ```
21 |
22 | If you are using [`electron-webpack-quick-start`](https://github.com/electron-userland/electron-webpack-quick-start) and have already installed dependecies, simply run the follwing to open your application in development mode.
23 | ```bash
24 | yarn dev
25 | ```
26 |
27 | ---
28 |
29 | ### Source Aliases
30 |
31 | `electron-webpack` provides a few [source aliases](https://webpack.js.org/configuration/resolve/#resolve-alias), or [path mappings](https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping) as TypeScript would call it, that allow you to import your modules easier.
32 |
33 | * `@`: Provides path to corresponding process (`main` or `renderer`)
34 | * `common`: Provides path to common source directory
35 |
36 |
37 | ---
38 |
39 | ### A little note on the *magic*
40 | `electron-webpack` may seem like a **magical** module to some from the outside, but let's cover a few topics about what's going on *under the hood*.
41 |
42 | #### Hot Module Replacement
43 | Webpack HMR has been setup to support both the `renderer` and `main` process. This allows for faster development ensuring your application is only restarted when necessary. All you need to do is accept hot updates in your entry files:
44 |
45 | ```js
46 | // in main/index.js, renderer/index.js or in both
47 | if (module.hot) {
48 | module.hot.accept();
49 | }
50 | ```
51 | Check out [this article](https://medium.com/@develar/electron-very-fast-developer-workflow-with-webpack-hmr-e2a2e23590ad) for more details on HMR in `electron-webpack` and the official [webpack docs](https://webpack.js.org/api/hot-module-replacement/) for details and options on the HMR API.
52 |
53 | #### Bundling for both `renderer` and `main` processes
54 | `webpack` is already setup to have an entry point for each process. In addition, you can also easily add further entry points to the `main` process when needed. In the other cases where you just need support for the `main` process, you can even skip the `renderer` process when needed ([more info](./configuration.md#source-directories)).
55 |
56 | #### Use of `html-webpack-plugin`
57 | You might notice that you don't need an `index.html` to get started on your application. That's because it is created for you, as it adds in a few extra configurations needed for the `electron` environment. If you are creating an `electron` application with `webpack`, you are most likely creating a Single Page Application anyways. So because of that, there is already a `` provided in the markup that you can mount your application onto.
58 |
59 | ### A Note for Windows Users
60 |
61 | If you run into errors while installing dependencies, related to `node-gyp`, then you most likely do not have the proper build tools installed on your system. Build tools include items like Python and Visual Studio.
62 |
63 | You can quickly install these build tools by globally installing [`windows-build-tools`](https://github.com/felixrieseberg/windows-build-tools). It provides many items like Visual C++ packages, Python, and much more.
64 |
--------------------------------------------------------------------------------
/docs/en/configuration.md:
--------------------------------------------------------------------------------
1 | As cool as `electron-webpack` can be a simple module to cover a majority of your development needs, you can also throw in your own configurations as needed. Please know that when using expected defaults, no configurations are neccessary.
2 |
3 | Configurations can be applied in `package.json` at `electronWebpack` or in a separate `electron-webpack.(json|json5|yml)`. For the purposes of this documentation, we will use the `package.json` approach and examples will show defaults when applicable.
4 |
5 | **Overview of available options**
6 | ```json
7 | "electronWebpack": {
8 | "commonSourceDirectory": "src/common",
9 | "staticSourceDirectory": "static",
10 | "title": true,
11 | "whiteListedModules": ["foo-ui-library"],
12 |
13 | "main": {
14 | "extraEntries": ["@/preload.js"],
15 | "sourceDirectory": "src/main",
16 | "webpackConfig": "custom.webpack.additions.js"
17 | },
18 |
19 | "renderer": {
20 | "dll": ["fooModule"],
21 | "sourceDirectory": "src/renderer",
22 | "template": "src/renderer/index.html",
23 | "webpackConfig": "custom.webpack.additions.js",
24 | "webpackDllConfig": "custom.webpackDll.additions.js"
25 | }
26 | }
27 | ```
28 |
29 | ---
30 |
31 | ### Source Directories
32 |
33 | Defines the path to a process's or common usage directory, relative to the project's root directory. `renderer.sourceDirectory` can be `null` if you don't desire `electron-webpack` to handle bundling.
34 |
35 | ```json
36 | "electronWebpack": {
37 | "commonSourceDirectory": "src/common",
38 | "staticSourceDirectory": "src/static",
39 | "main": {
40 | "sourceDirectory": "src/main"
41 | },
42 | "renderer": {
43 | "sourceDirectory": "src/renderer"
44 | }
45 | }
46 | ```
47 |
48 | ### BrowserWindow Title
49 |
50 | Defines default BrowserWindow title.
51 | * `true` (default): Title is automatically set based on `package.json` name or `productName` when using `electron-builder`
52 | * **String**: Use a custom string for title
53 |
54 | ```json
55 | "electronWebpack": {
56 | "title": true,
57 |
58 | /* or */
59 |
60 | "title": "My Custom Title"
61 | }
62 | ```
63 |
64 | ### Additional Entry Points for `main` process
65 | For those situtations where you need additional [entry points](https://webpack.js.org/concepts/entry-points/). Can be useful when you need a preload script for a BrowserWindow.
66 |
67 | ```json
68 | "electronWebpack": {
69 | "main": {
70 | "extraEntries": ["@/preload.js"]
71 | }
72 | }
73 | ```
74 | Note that you can use the `@` alias to reference your `main.sourceDirectory`.
75 |
76 | ### Dll bundle splitting
77 | See [Dll Bundle Splitting](./dll-bundle-splitting.md) for more info.
78 |
79 | ```json
80 | "electronWebpack": {
81 | "renderer": {
82 | "dll": ["fooModule"]
83 | }
84 | }
85 | ```
86 |
87 | ### White-listing Externals
88 | Since `webpack` is set to target the `electron` environment, all modules are treated as [externals](https://webpack.js.org/configuration/externals/). Unfortunately, there can be a few situations where this behavior may not be expected by some modules. For the case of some Vue UI libraries that provide raw `*.vue` components, they will needed to be white-listed. This ensures that `vue-loader` is able to compile them as the UI library originally expected.
89 |
90 | ```json
91 | "electronWebpack": {
92 | "whiteListedModules": ["foo-ui-library"]
93 | }
94 | ```
95 |
96 | ### Modified Webpack Configurations
97 | See [Modifying Webpack Configurations](modifying-webpack-configurations.md) for more information.
98 |
99 | ```json
100 | "electronWebpack": {
101 | "main": {
102 | "webpackConfig": "custom.additions.webpack.js"
103 | },
104 |
105 | "renderer": {
106 | "webpackConfig": "custom.additions.webpack.js",
107 | "webpackDllConfig": "custom.additions.webpack.js"
108 | }
109 | }
110 | ```
111 |
--------------------------------------------------------------------------------
/packages/electron-webpack/src/dev/WebpackDevServerManager.ts:
--------------------------------------------------------------------------------
1 | import chalk from "chalk"
2 | import { ChildProcess } from "child_process"
3 | import * as path from "path"
4 | import { createConfigurator } from "../main"
5 | import { statOrNull } from "../util"
6 | import { ChildProcessManager, PromiseNotifier, run } from "./ChildProcessManager"
7 | import { LineFilter, logError, logProcess, logProcessErrorOutput } from "./devUtil"
8 |
9 | const debug = require("debug")("electron-webpack")
10 |
11 | function runWds(projectDir: string, env: any) {
12 | const isWin = process.platform === "win32"
13 | const webpackDevServerPath = require.resolve(path.join(".bin", "webpack-dev-server" + (isWin ? ".cmd" : "")))
14 | debug(`Start renderer WDS ${webpackDevServerPath} on ${env.ELECTRON_WEBPACK_WDS_PORT} port`)
15 | return run(webpackDevServerPath, ["--color", "--env.autoClean=false", "--config", path.join(__dirname, "../../webpack.renderer.config.js")], {
16 | env,
17 | cwd: projectDir,
18 | })
19 | }
20 |
21 | // 1. in another process to speedup compilation
22 | // 2. some loaders detect webpack-dev-server hot mode only if run as CLI
23 | export async function startRenderer(projectDir: string, env: any) {
24 | const webpackConfigurator = await createConfigurator("renderer", {production: false, configuration: {projectDir}})
25 | const sourceDir = webpackConfigurator.sourceDir
26 | // explicitly set to null - do not handle at all and do not show info message
27 | if (sourceDir === null) {
28 | return
29 | }
30 |
31 | const dirStat = await statOrNull(sourceDir)
32 | if (dirStat == null || !dirStat.isDirectory()) {
33 | logProcess("Renderer", `No renderer source directory (${path.relative(projectDir, sourceDir)})`, chalk.blue)
34 | return
35 | }
36 |
37 | if (webpackConfigurator.hasDependency("electron-next")) {
38 | debug(`Renderer WDS is not started - there is electron-next dependency`)
39 | return
40 | }
41 |
42 | const lineFilter = new CompoundRendererLineFilter([
43 | new OneTimeLineFilter("Project is running at "),
44 | new OneTimeLineFilter("webpack output is served from "),
45 | ])
46 | return await new Promise((resolve: (() => void) | null, reject: ((error: Error) => void) | null) => {
47 | let devServerProcess: ChildProcess | null
48 | try {
49 | devServerProcess = runWds(projectDir, env)
50 | }
51 | catch (e) {
52 | reject!(e)
53 | return
54 | }
55 |
56 | //tslint:disable-next-line:no-unused-expression
57 | new ChildProcessManager(devServerProcess, "Renderer WDS", new PromiseNotifier(resolve, reject))
58 | devServerProcess.on("error", error => {
59 | if (reject == null) {
60 | logError("Renderer", error)
61 | }
62 | else {
63 | reject(error)
64 | reject = null
65 | }
66 | })
67 |
68 | devServerProcess.stdout!!.on("data", (data: string) => {
69 | logProcess("Renderer", data, chalk.blue, lineFilter)
70 |
71 | const r = resolve
72 | // we must resolve only after compilation, otherwise devtools disconnected
73 | if (r != null && (data.includes(": Compiled successfully.") || data.includes(": Compiled with warnings."))) {
74 | resolve = null
75 | r()
76 | }
77 | })
78 |
79 | logProcessErrorOutput("Renderer", devServerProcess)
80 | })
81 | }
82 |
83 | class OneTimeLineFilter implements LineFilter {
84 | private filtered = false
85 |
86 | constructor(private readonly prefix: string) {
87 | }
88 |
89 | filter(line: string) {
90 | if (!this.filtered && line.startsWith(this.prefix)) {
91 | this.filtered = true
92 | return false
93 |
94 | }
95 | return true
96 | }
97 | }
98 |
99 | class CompoundRendererLineFilter implements LineFilter {
100 | constructor(private readonly filters: Array) {
101 | }
102 |
103 | filter(line: string) {
104 | return !this.filters.some(it => !it.filter(line))
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # electron-webpack [](https://npmjs.org/package/electron-webpack) [](https://github.com/electron-userland/electron-webpack)
2 |
3 |
4 | Project Status: maintenance mode. **Consider using [Vite](https://github.com/cawa-93/vite-electron-builder) instead of Webpack.**
5 |
6 | > Because setting up `webpack` in the `electron` environment shouldn't be difficult.
7 |
8 | ## Maintainer Needed
9 | This project is looking for active maintainers. Feel free to post [here](https://github.com/electron-userland/electron-webpack/issues/428).
10 |
11 | ## Overview
12 | Modern web development practices today require a lot of setup with things like `webpack` to bundle your code, `babel` for transpiling, `eslint` for linting, and so much more that the list just goes on. Unfortunately when creating `electron` applications, all of that setup just became much more difficult. The primary aim of `electron-webpack` is to eliminate all preliminary setup with one simple install so you can get back to developing your application.
13 |
14 | > Why create a module and not a full boilerplate?
15 |
16 | If you've been in the JavaScript world for even a short period of time, you are very aware that things are always changing, and development setup is no exclusion. Putting all development scripts into a single **updatable** module just makes sense. Sure a full featured boilerplate works too, but doing also involves needing to manually update those pesky `webpack` configuration files that some may call *magic* when something new comes out.
17 |
18 | Here are some of the awesome features you'll find using `electron-webpack`...
19 |
20 | * Detailed [documentation](https://webpack.electron.build)
21 | * Use of [`webpack`](https://webpack.js.org/) for source code bundling
22 | * Use of [`webpack-dev-server`](https://github.com/webpack/webpack-dev-server) for development
23 | * HMR for both `renderer` and `main` processes
24 | * Use of [`@babel/preset-env`](https://github.com/babel/babel/tree/master/packages/babel-preset-env) that is automatically configured based on your `electron` version
25 | * Ability to add custom `webpack` loaders, plugins, etc.
26 | * [Add-ons](https://webpack.electron.build/add-ons) to support items like [TypeScript](http://www.typescriptlang.org/), [Less](http://lesscss.org/), [EJS](http://www.embeddedjs.com/), etc.
27 |
28 | ## Quick Start
29 | Get started fast with [electron-webpack-quick-start](https://github.com/electron-userland/electron-webpack-quick-start).
30 | ```bash
31 | # create a directory of your choice, and copy template using curl
32 | mkdir my-project && cd my-project
33 | curl -fsSL https://github.com/electron-userland/electron-webpack-quick-start/archive/master.tar.gz | tar -xz --strip-components 1
34 |
35 | # or copy template using git clone
36 | git clone https://github.com/electron-userland/electron-webpack-quick-start.git
37 | cd electron-webpack-quick-start
38 | rm -rf .git
39 |
40 | # install dependencies
41 | yarn
42 | ```
43 |
44 | If you already have an existing project, or are looking for a custom approach outside of the quick start template, make sure to read over the [Core Concepts](https://webpack.electron.build/core-concepts), [Project Structure](https://webpack.electron.build/project-structure), and [Development](https://webpack.electron.build/development) sections of `electron-webpack`'s documentation.
45 |
46 | ### Next Steps
47 | Make sure to take advantage of the detailed [documentation](https://webpack.electron.build) that `electron-webpack` provides. It covers everything from how things work internally, adding custom configurations, and building your application.
48 |
49 | ### Contributing
50 | Feel free to grab an issue and fix it or to share your features and improvements - PRs are always welcome!
51 | However, in order for your contribution to be property included in the automatically generated release notes, please use our [standard format](https://gist.github.com/develar/273e2eb938792cf5f86451fbac2bcd51) for your commit messages.
52 |
--------------------------------------------------------------------------------
/packages/electron-webpack/scheme.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "additionalProperties": false,
4 | "definitions": {
5 | "ElectronWebpackConfigurationMain": {
6 | "additionalProperties": false,
7 | "properties": {
8 | "extraEntries": {
9 | "anyOf": [
10 | {
11 | "items": {
12 | "type": "string"
13 | },
14 | "type": "array"
15 | },
16 | {
17 | "additionalProperties": {
18 | "anyOf": [
19 | {
20 | "type": "string"
21 | },
22 | {
23 | "items": {
24 | "type": "string"
25 | },
26 | "type": "array"
27 | }
28 | ]
29 | },
30 | "type": "object"
31 | },
32 | {
33 | "type": "string"
34 | }
35 | ],
36 | "description": "The extra [entry points](https://webpack.js.org/concepts/entry-points/)."
37 | },
38 | "sourceDirectory": {
39 | "type": [
40 | "string",
41 | "null"
42 | ]
43 | },
44 | "webpackConfig": {
45 | "type": [
46 | "string",
47 | "null"
48 | ]
49 | }
50 | },
51 | "type": "object"
52 | },
53 | "ElectronWebpackConfigurationRenderer": {
54 | "additionalProperties": false,
55 | "properties": {
56 | "dll": {
57 | "anyOf": [
58 | {
59 | "items": {
60 | "type": "string"
61 | },
62 | "type": "array"
63 | },
64 | {
65 | "type": "object"
66 | },
67 | {
68 | "type": "null"
69 | }
70 | ]
71 | },
72 | "sourceDirectory": {
73 | "type": [
74 | "string",
75 | "null"
76 | ]
77 | },
78 | "template": {
79 | "type": [
80 | "string",
81 | "null"
82 | ]
83 | },
84 | "webpackConfig": {
85 | "type": [
86 | "string",
87 | "null"
88 | ]
89 | },
90 | "webpackDllConfig": {
91 | "type": [
92 | "string",
93 | "null"
94 | ]
95 | }
96 | },
97 | "type": "object"
98 | }
99 | },
100 | "properties": {
101 | "commonDistDirectory": {
102 | "type": [
103 | "string",
104 | "null"
105 | ]
106 | },
107 | "commonSourceDirectory": {
108 | "type": [
109 | "string",
110 | "null"
111 | ]
112 | },
113 | "electronVersion": {
114 | "type": "string"
115 | },
116 | "externals": {
117 | "items": {
118 | "type": "string"
119 | },
120 | "type": "array"
121 | },
122 | "main": {
123 | "anyOf": [
124 | {
125 | "$ref": "#/definitions/ElectronWebpackConfigurationMain"
126 | },
127 | {
128 | "type": "null"
129 | }
130 | ]
131 | },
132 | "projectDir": {
133 | "type": [
134 | "string",
135 | "null"
136 | ]
137 | },
138 | "renderer": {
139 | "anyOf": [
140 | {
141 | "$ref": "#/definitions/ElectronWebpackConfigurationRenderer"
142 | },
143 | {
144 | "type": "null"
145 | }
146 | ]
147 | },
148 | "staticSourceDirectory": {
149 | "type": [
150 | "string",
151 | "null"
152 | ]
153 | },
154 | "title": {
155 | "type": [
156 | "string",
157 | "boolean",
158 | "null"
159 | ]
160 | },
161 | "whiteListedModules": {
162 | "items": {
163 | "type": "string"
164 | },
165 | "type": "array"
166 | }
167 | },
168 | "type": "object"
169 | }
--------------------------------------------------------------------------------
/.idea/codeStyleSettings.xml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/test/out/__snapshots__/loaderDetectionTest.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`nunjucks 1`] = `
4 | "./src/main/index.js
5 | Module not found: Error: Can't resolve 'nunjucks-loader' in ''
6 | resolve 'nunjucks-loader' in ''
7 | Parsed request is a module
8 | using description file: /package.json (relative path: .)
9 | resolve as module
10 | looking for modules in /node_modules
11 | using description file: /package.json (relative path: ./node_modules)
12 | using description file: /package.json (relative path: ./node_modules/nunjucks-loader)
13 | no extension
14 | /node_modules/nunjucks-loader doesn't exist
15 | .js
16 | /node_modules/nunjucks-loader.js doesn't exist
17 | .json
18 | /node_modules/nunjucks-loader.json doesn't exist
19 | as directory
20 | /node_modules/nunjucks-loader doesn't exist
21 | looking for modules in /packages/electron-webpack/node_modules
22 | using description file: /packages/electron-webpack/package.json (relative path: ./node_modules)
23 | using description file: /packages/electron-webpack/package.json (relative path: ./node_modules/nunjucks-loader)
24 | no extension
25 | /packages/electron-webpack/node_modules/nunjucks-loader doesn't exist
26 | .js
27 | /packages/electron-webpack/node_modules/nunjucks-loader.js doesn't exist
28 | .json
29 | /packages/electron-webpack/node_modules/nunjucks-loader.json doesn't exist
30 | as directory
31 | /packages/electron-webpack/node_modules/nunjucks-loader doesn't exist
32 | [/node_modules/nunjucks-loader]
33 | [/node_modules/nunjucks-loader.js]
34 | [/node_modules/nunjucks-loader.json]
35 | [/packages/electron-webpack/node_modules/nunjucks-loader]
36 | [/packages/electron-webpack/node_modules/nunjucks-loader.js]
37 | [/packages/electron-webpack/node_modules/nunjucks-loader.json]
38 | @ ./src/main/index.js 3:0-21
39 | @ multi ./src/main/index.js"
40 | `;
41 |
42 | exports[`react 1`] = `"Cannot find module '@babel/preset-react' from 'js.js'"`;
43 |
44 | exports[`sass 1`] = `
45 | "./src/renderer/index.js
46 | Module not found: Error: Can't resolve 'sass-loader' in ''
47 | resolve 'sass-loader' in ''
48 | Parsed request is a module
49 | using description file: /package.json (relative path: .)
50 | resolve as module
51 | looking for modules in /node_modules
52 | using description file: /package.json (relative path: ./node_modules)
53 | using description file: /package.json (relative path: ./node_modules/sass-loader)
54 | no extension
55 | /node_modules/sass-loader doesn't exist
56 | .js
57 | /node_modules/sass-loader.js doesn't exist
58 | .json
59 | /node_modules/sass-loader.json doesn't exist
60 | as directory
61 | /node_modules/sass-loader doesn't exist
62 | looking for modules in /packages/electron-webpack/node_modules
63 | using description file: /packages/electron-webpack/package.json (relative path: ./node_modules)
64 | using description file: /packages/electron-webpack/package.json (relative path: ./node_modules/sass-loader)
65 | no extension
66 | /packages/electron-webpack/node_modules/sass-loader doesn't exist
67 | .js
68 | /packages/electron-webpack/node_modules/sass-loader.js doesn't exist
69 | .json
70 | /packages/electron-webpack/node_modules/sass-loader.json doesn't exist
71 | as directory
72 | /packages/electron-webpack/node_modules/sass-loader doesn't exist
73 | [/node_modules/sass-loader]
74 | [/node_modules/sass-loader.js]
75 | [/node_modules/sass-loader.json]
76 | [/packages/electron-webpack/node_modules/sass-loader]
77 | [/packages/electron-webpack/node_modules/sass-loader.js]
78 | [/packages/electron-webpack/node_modules/sass-loader.json]
79 | @ ./src/renderer/index.js 3:0-22
80 | @ multi ./src/renderer/index.js"
81 | `;
82 |
--------------------------------------------------------------------------------
/docs/en/modifying-webpack-configurations.md:
--------------------------------------------------------------------------------
1 | Another great benefit of using `electron-wepback` is that you are never restricted to an abstracted API. Of course there isn't a configuration that fits everybody's need, and `electron-webpack` is aware of that.
2 |
3 | Custom modifications can be made for the `renderer`, `renderer-dll`, and `main` processes.
4 | Notice that the full `webpack` API is usable and not blocked. You could add any loaders and plugins, but keep in mind that making major modifications may break expected behavior.
5 |
6 | There are currently two methods of modifying the webpack configuration:
7 |
8 | - [Using a config object](#using-a-config-object)
9 | - [Using a config function](#using-a-config-function)
10 |
11 | The benefit of using either method, as opposed to [Extending as a Library](extending-as-a-library.md), is that you don't lose access to the `electron-webpack` CLI, which is very beneficial for development.
12 |
13 | ## Using a config object
14 |
15 | Thanks to the power of [webpack-merge](https://github.com/survivejs/webpack-merge), it is possible to add your own webpack loaders, plugins, etc, by simply providing a config object that will be automatically merged on top of the predefined configuration using the [smart merging strategy](https://github.com/survivejs/webpack-merge#smart-merging).
16 |
17 |
18 | ### Use Case
19 |
20 | Let's say our project needs to be able to import `*.txt` files in the `renderer` process. We can use `raw-loader` to get that done.
21 |
22 |
23 | #### Install `raw-loader`
24 |
25 | ```bash
26 | yarn add raw-loader --dev
27 | ```
28 |
29 | #### Configure `electron-webpack`
30 |
31 | Provide a configuration for `electron-webpack`, for example in your `package.json` file, and point it to a custom webpack config file for the renderer process:
32 | ```json
33 | {
34 | "electronWebpack": {
35 | "renderer": {
36 | "webpackConfig": "webpack.renderer.additions.js"
37 | }
38 | }
39 | }
40 | ```
41 | See [Configuration](configuration.md) for more information.
42 |
43 | #### Configure `raw-loader`
44 |
45 | ```js tab="webpack.renderer.additions.js"
46 | module.exports = {
47 | module: {
48 | rules: [
49 | {
50 | test: /\.txt$/,
51 | use: 'raw-loader'
52 | }
53 | ]
54 | }
55 | }
56 | ```
57 |
58 |
59 | Now when running `electron-webpack`'s CLI, your custom additions will be loaded and you can use them as expected.
60 |
61 | This is a very convenient way to add configuration on top of the defaults, but it has some limitations when it comes to modifying defaults or even removing parts of the configuration.
62 |
63 | If you need more control over the configuration, then maybe a [config function](#using-a-config-function) or [Extending as a Library](extending-as-a-library.md) can better suit your needs.
64 |
65 | ## Using a config function
66 |
67 | Beginning with version 2.7.0, `electron-webpack` supports configuration modules that export a function instead of an object.
68 |
69 | In this case, the config function you provide will be invoked with the default config object as an argument, and it is expected to return the final config object.
70 |
71 | You may modify the default object using any method you like, for example you could iterate over loaders or plugins and remove or replace some of them. You could use a tool like `webpack-merge` manually, or you could even create and return an entirely new object if that suits your needs.
72 |
73 |
74 | ### Use Case
75 |
76 | Let's say you would like the `css-loader` to treat everything as [CSS modules](https://github.com/webpack-contrib/css-loader#modules) and implicitly convert all class names to hashed variants, unless you defined them as `:global` explicitly.
77 |
78 | First, configure `electron-webpack` to use your custom config file.
79 |
80 | Note that while the filename technically doesn't matter, we do not use "additions" in the name as we did before, as this config file provides more than just additions.
81 |
82 | ```json
83 | {
84 | "electronWebpack": {
85 | "renderer": {
86 | "webpackConfig": "webpack.renderer.js"
87 | }
88 | }
89 | }
90 | ```
91 | See [Configuration](configuration.md) for more information.
92 |
93 | Next, provide your custom configuration file:
94 |
95 | ```js tab="webpack.renderer.js"
96 | module.exports = function(config) {
97 | const styleRules = config.module.rules.filter(rule =>
98 | rule.test.toString().match(/css|less|s\(\[ac\]\)ss/)
99 | )
100 |
101 | styleRules.forEach(rule => {
102 | const cssLoader = rule.use.find(use => use.loader === 'css-loader')
103 | // this is the actual modification we make:
104 | cssLoader.options.modules = 'local'
105 | })
106 |
107 | return config
108 | }
109 | ```
110 |
111 | As you can see, instead of exporting an object, we export a function.
112 | The function receives the webpack config object, simply modifies it in certain places and then returns it again.
113 |
114 | When in doubt about the structure of the config object, you can either get familiar with the [sources that define it](https://github.com/electron-userland/electron-webpack/tree/master/packages/electron-webpack/src/targets) or simply try a quick `console.log(config)` inside of the function.
115 | Or better yet, try `console.log(JSON.stringify(config, null, 4))` to reveal all the nested values in a readable format.
116 |
117 | If you need even more control over the configuration, then maybe [Extending as a Library](extending-as-a-library.md) can better suit your needs.
118 |
--------------------------------------------------------------------------------
/docs/en/add-ons.md:
--------------------------------------------------------------------------------
1 | Although `electron-webpack` is provided as a single module, you can also install add-ons. Add-ons are made available to help setup certain frameworks that may require a lot of extra configuration or dependencies, such as TypeScript or Vue.js.
2 |
3 | These add-ons are completely optional and may not support all use cases. If you need something more custom, please know that the entirety of `webpack`'s documentation still applies to `electron-webpack` ([more info](./extending-as-a-library.md)).
4 |
5 | Current Add-ons:
6 |
7 | * [JavaScript Frameworks](#javascript-frameworks)
8 | * [Vue.js](#vuejs)
9 | * [React JSX](#react-jsx)
10 | * [Pre-processors](#pre-processors)
11 | * [ESLint](#eslint)
12 | * [TypeScript](#typescript)
13 | * [Less](#less)
14 | * [Sass/SCSS](#sassscss)
15 | * [EJS](#ejs)
16 | * [Nunjucks](#nunjucks)
17 | * [UI Libraries](#ui-libraries)
18 | * [iView](#iview)
19 | * [Miscellaneous](#miscellaneous)
20 | * [Build Notifications](#build-notifications)
21 |
22 | ---
23 |
24 | ## JavaScript Frameworks
25 |
26 | ### Vue.js
27 | Adds support for Vue component files through the use of `vue-loader`. In addition, `vue-devtools` will also be installed for development purposes.
28 |
29 | ```bash
30 | yarn add vue electron-webpack-vue --dev
31 | ```
32 |
33 | #### Adding TypeScript support
34 | Install the [TypeScript](#typescript) add-on, followed by adding the below file to shim Vue component files.
35 |
36 | ```ts tab="src/renderer/vue-shims.d.ts"
37 | declare module '*.vue' {
38 | import Vue from 'vue'
39 | export default Vue
40 | }
41 | ```
42 |
43 | And of course, make sure to let `vue-loader` know you want to use TypeScript in your component file using the `lang="ts"` attribute.
44 |
45 | ```html
46 |
47 |
48 |
51 |
52 |
53 | ```
54 |
55 | #### Adding ESLint support
56 | Install the [ESLint](#eslint) add-on, install `eslint-plugin-html`, and add the following additional configurations.
57 |
58 | Install `html` plugin to lint Vue component files:
59 | ```bash
60 | yarn add eslint-plugin-html --dev
61 | ```
62 |
63 | ```js tab=".eslintrc.js"
64 | module.exports = {
65 | plugins: [
66 | 'html'
67 | ]
68 | }
69 | ```
70 |
71 | ### React JSX
72 | Add support for compiling JSX files:
73 |
74 | ```bash
75 | yarn add @babel/preset-react --dev
76 | ```
77 |
78 | ### Adding TypeScript support for JSX (`.tsx` files)
79 | Install the [TypeScript](#typescript) add-on, followed by extending your `tsconfig.json` to include the jsx compiler option as below:
80 |
81 | ```json tab="tsconfig.json"
82 | {
83 | "extends": "./node_modules/electron-webpack/tsconfig-base.json",
84 | "compilerOptions": {
85 | "jsx": "react"
86 | }
87 | }
88 | ```
89 |
90 | ---
91 |
92 | ## Pre-processors
93 |
94 | ### Babel
95 |
96 | [@babel/preset-env](https://github.com/babel/babel/tree/master/packages/babel-preset-env) is automatically configured based on your `electron` version.
97 |
98 | All direct dev dependencies `babel-preset-*` and `babel-plugin-*` are automatically configured also.
99 |
100 | ### ESLint
101 | Add support for script file linting using `eslint`. Internally uses `eslint`, `eslint-loader`, `eslint-friendly-formatter`, and makes `babel-eslint` available if needed.
102 |
103 | ```bash
104 | yarn add electron-webpack-eslint --dev
105 | ```
106 |
107 | Create `.eslintrc.js` in the root directory:
108 |
109 | ```js tab=".eslintrc.js"
110 | module.exports = {
111 | /* your base configuration of choice */
112 | extends: 'eslint:recommended',
113 |
114 | parser: 'babel-eslint',
115 | parserOptions: {
116 | sourceType: 'module'
117 | },
118 | env: {
119 | browser: true,
120 | node: true
121 | },
122 | globals: {
123 | __static: true
124 | }
125 | }
126 | ```
127 |
128 | ### TypeScript
129 | Add support for compiling TypeScript script files. Internally uses both `ts-loader` and `fork-ts-checker-webpack-plugin` to compile `*.ts`. Note that entry files can also use the `*.ts` extension.
130 |
131 | ```bash
132 | yarn add typescript electron-webpack-ts --dev
133 | ```
134 |
135 | Create `tsconfig.json` in the root directory:
136 | ```json
137 | {
138 | "extends": "./node_modules/electron-webpack/tsconfig-base.json"
139 | }
140 | ```
141 |
142 | ### Less
143 | Add support for compiling Less style files.
144 |
145 | ```bash
146 | yarn add less less-loader --dev
147 | ```
148 |
149 | ### Sass/SCSS
150 | Add support for compiling Sass/SCSS style files.
151 |
152 | ```bash
153 | yarn add node-sass sass-loader --dev
154 | ```
155 |
156 | ### EJS
157 | Add support for compiling EJS template files.
158 |
159 | ```bash
160 | yarn add ejs ejs-html-loader --dev
161 | ```
162 |
163 | ### Nunjucks
164 | Add support for compiling Nunjucks template files:
165 |
166 | ```bash
167 | yarn add nunjucks nunjucks-loader --dev
168 | ```
169 |
170 | ---
171 |
172 | ## UI Libraries
173 |
174 | ### iView
175 | Once you have the [Vue.js](#vuejs) add-on installed, `electron-webpack` will internally support iView's "import on demand" feature. No further setup is necessary.
176 |
177 | ### Element
178 | Once you have the [element-ui](https://github.com/ElemeFE/element) installed, `electron-webpack` will internally support Element's "import on demand" feature. No further setup is necessary.
179 |
180 | ---
181 |
182 | ## Miscellaneous
183 |
184 | ### Build Notifications
185 | Provide OS-level notifications from `webpack` during development.
186 |
187 | ```bash
188 | yarn add webpack-build-notifier --dev
189 | ```
190 |
--------------------------------------------------------------------------------
/test/src/helpers/helper.ts:
--------------------------------------------------------------------------------
1 | import { ElectronWebpackConfiguration } from "electron-webpack"
2 | import { copy, emptyDir } from "fs-extra"
3 | import MemoryFS from "memory-fs"
4 | import * as path from "path"
5 | import { TmpDir } from "temp-file"
6 | import webpack, { Configuration, Stats } from "webpack"
7 |
8 | export const tmpDir = new TmpDir()
9 |
10 | export const rootDir = path.join(__dirname, "..", "..", "..")
11 |
12 | export async function doTest(configurationFile: string, electronWebpackConfiguration?: ElectronWebpackConfiguration) {
13 | const projectDir = await getMutableProjectDir()
14 | const finalConfiguration = {projectDir, ...electronWebpackConfiguration}
15 | const configuration = await require(`electron-webpack/${configurationFile}`)({configuration: finalConfiguration, production: true})
16 | await testWebpack(configuration, projectDir)
17 | }
18 |
19 | export async function getMutableProjectDir(fixtureName = "simple") {
20 | const projectDir = process.env.TEST_APP_TMP_DIR || await tmpDir.getTempDir()
21 | await emptyDir(projectDir)
22 | await copy(path.join(rootDir, "test/fixtures", fixtureName), projectDir)
23 | return projectDir
24 | }
25 |
26 | export async function testWebpack(configuration: Configuration, projectDir: string, checkCompilation = true) {
27 | if (Array.isArray(configuration)) {
28 | configuration.forEach(addCustomResolver)
29 | }
30 | else {
31 | addCustomResolver(configuration)
32 | }
33 |
34 | const fs = new MemoryFS()
35 | const stats = await new Promise((resolve, reject) => {
36 | compile(fs, configuration, resolve, reject)
37 | })
38 |
39 | if (checkCompilation) {
40 | expect(statToMatchObject(stats, projectDir, fs)).toMatchSnapshot()
41 | expect(bufferToString(fs.meta(projectDir), projectDir)).toMatchSnapshot()
42 | }
43 | return fs
44 | }
45 |
46 | function addCustomResolver(configuration: Configuration) {
47 | expect(configuration.resolveLoader).toBeUndefined()
48 | configuration.resolveLoader = {
49 | modules: [path.join(rootDir, "node_modules"), path.join(rootDir, "packages/electron-webpack/node_modules")]
50 | }
51 | }
52 |
53 | function statToMatchObject(stats: Stats, projectDir: string, fs: MemoryFS) {
54 | if (stats.hasErrors()) {
55 | console.log(stats.toString({colors: true}))
56 | // console.log("FS data: " + util.inspect(fs, {colors: true}))
57 | throw new Error(stats.toJson().errors.join("\n"))
58 | }
59 |
60 | // skip first 3 lines - Hash, Version and Time
61 | return stats.toString()
62 | .split(/\r?\n/)
63 | .filter(it => {
64 | const trimmed = it.trim()
65 | return !trimmed.startsWith("Time:") && !trimmed.startsWith("Hash:") && !trimmed.startsWith("Version:") && !trimmed.startsWith("Built at:")
66 | })
67 | .join("\n")
68 | .replace(/\\/g, "/")
69 | .replace(new RegExp(`[./]*${projectDir}`, "g"), "")
70 | // no idea why failed on CI - in any case we validate file content
71 | .replace(/\/style\.css \d+ bytes/g, "/style.css")
72 | .replace(new RegExp(`[./]*${process.cwd()}`, "g"), "")
73 | .replace(new RegExp("[.]*/[^\n]+node_modules", "g"), "/node_modules")
74 | }
75 |
76 | function compile(fs: any, configuration: Configuration, resolve: (stats: Stats) => void, reject: (error?: Error) => void) {
77 | const compiler = webpack(configuration)
78 | compiler.outputFileSystem = fs
79 | compiler.run((error, stats) => {
80 | if (error != null) {
81 | reject(error)
82 | return
83 | }
84 |
85 | resolve(stats)
86 | })
87 | }
88 |
89 | export function bufferToString(host: any, projectDir: string) {
90 | for (const key of Object.getOwnPropertyNames(host)) {
91 | if (key === "") {
92 | delete host[key]
93 | }
94 |
95 | const value = host[key]
96 | if (value == null) {
97 | continue
98 | }
99 |
100 | if (Buffer.isBuffer(value)) {
101 | host[key] = removeNotStableValues(value.toString())
102 | .replace(new RegExp(projectDir, "g"), "")
103 | .replace(new RegExp(rootDir, "g"), "")
104 | }
105 | else if (typeof value === "object") {
106 | bufferToString(value, projectDir)
107 | }
108 | }
109 | return host
110 | }
111 |
112 | function removeNotStableValues(value: string) {
113 | const bootstrap = "webpack:///webpack/bootstrap"
114 | const index = value.indexOf(bootstrap)
115 | if (index > 0) {
116 | return value.substring(0, index + bootstrap.length) + value.substring(value.indexOf('"', index))
117 | }
118 |
119 | return value
120 | }
121 |
122 | export function assertThat(actual: any): Assertions {
123 | return new Assertions(actual)
124 | }
125 |
126 | class Assertions {
127 | constructor(private actual: any) {
128 | }
129 |
130 | containsAll(expected: Iterable) {
131 | expect(this.actual.slice().sort()).toEqual(Array.from(expected).slice().sort())
132 | }
133 |
134 | async throws(projectDir: string) {
135 | let actualError: Error | null = null
136 | let result: any
137 | try {
138 | result = await this.actual
139 | }
140 | catch (e) {
141 | actualError = e
142 | }
143 |
144 | let m
145 | if (actualError == null) {
146 | m = result
147 | }
148 | else {
149 | m = actualError.message
150 | m = m.toString()
151 | .replace(new RegExp(projectDir, "g"), "")
152 | .replace(new RegExp(rootDir, "g"), "")
153 | }
154 | try {
155 | expect(m).toMatchSnapshot()
156 | }
157 | catch (matchError) {
158 | throw new Error(matchError + " " + actualError)
159 | }
160 | }
161 | }
--------------------------------------------------------------------------------
/packages/electron-webpack/src/targets/BaseTarget.ts:
--------------------------------------------------------------------------------
1 | import * as path from "path"
2 | import { DefinePlugin, EnvironmentPlugin, HotModuleReplacementPlugin, LoaderOptionsPlugin, version as webpackVersion } from "webpack"
3 | import { getDefaultRelativeSystemDependentCommonSource } from "../config"
4 | import { configureDll } from "../configurators/dll"
5 | import { configureEslint } from "../configurators/eslint"
6 | import { createBabelLoader } from "../configurators/js"
7 | import { WebpackConfigurator } from "../main"
8 | import { WatchFilterPlugin } from "../plugins/WatchMatchPlugin"
9 | import { WebpackRemoveOldAssetsPlugin } from "../plugins/WebpackRemoveOldAssetsPlugin"
10 |
11 | export class BaseTarget {
12 | configureRules(configurator: WebpackConfigurator): void {
13 | const rules = configurator.rules
14 |
15 | const babelLoader = createBabelLoader(configurator)
16 | // noinspection SpellCheckingInspection
17 | if (configurator.type !== "main" && configurator.hasDependency("iview")) {
18 | rules.push({
19 | test: /iview.src.*?js$/,
20 | use: babelLoader
21 | })
22 | }
23 |
24 | rules.push(
25 | {
26 | test: /\.js$/,
27 | exclude: /(node_modules|bower_components)/,
28 | use: babelLoader
29 | },
30 | {
31 | test: /\.node$/,
32 | use: "node-loader"
33 | },
34 | )
35 |
36 | if (configurator.hasDevDependency("nunjucks-loader")) {
37 | rules.push({
38 | test: /\.(njk|nunjucks)$/,
39 | loader: "nunjucks-loader"
40 | })
41 | }
42 |
43 | configureEslint(configurator)
44 | }
45 |
46 | async configurePlugins(configurator: WebpackConfigurator): Promise {
47 | const plugins = configurator.plugins
48 |
49 | const dllManifest = await configureDll(configurator)
50 | const mode = configurator.isProduction ? "production" : "development"
51 |
52 | let optimization = configurator.config.optimization
53 | if (optimization == null) {
54 | optimization = {}
55 | configurator.config.optimization = optimization
56 | }
57 |
58 | optimization.nodeEnv = mode
59 | configurator.config.mode = mode
60 |
61 | if (configurator.isProduction) {
62 | if (configurator.env.minify !== false) {
63 | const TerserPlugin = require("terser-webpack-plugin")
64 | // noinspection SpellCheckingInspection
65 | optimization.minimizer = [new TerserPlugin({
66 | parallel: true,
67 | sourceMap: true,
68 | terserOptions: {
69 | keep_fnames: true,
70 | },
71 | })]
72 | }
73 | optimization.minimize = true
74 | plugins.push(new LoaderOptionsPlugin({minimize: true}))
75 |
76 | // do not use ModuleConcatenationPlugin for HMR
77 | // https://github.com/webpack/webpack-dev-server/issues/949
78 | optimization.concatenateModules = true
79 | }
80 | else {
81 | configureDevelopmentPlugins(configurator)
82 | }
83 |
84 | if (configurator.env.autoClean !== false) {
85 | plugins.push(new WebpackRemoveOldAssetsPlugin(dllManifest))
86 | }
87 |
88 | optimization.noEmitOnErrors = true
89 |
90 | const additionalEnvironmentVariables = Object.keys(process.env).filter(it => it.startsWith("ELECTRON_WEBPACK_"))
91 | if (additionalEnvironmentVariables.length > 0) {
92 | plugins.push(new EnvironmentPlugin(additionalEnvironmentVariables))
93 | }
94 | }
95 | }
96 |
97 | export function configureFileLoader(prefix: string, limit = 10 * 1024) {
98 | return {
99 | limit,
100 | name: `${prefix}/[name]--[folder].[ext]`
101 | }
102 | }
103 |
104 | function isAncestor(file: string, dir: string) {
105 | if (file === dir) {
106 | return true
107 | }
108 | return file.length > dir.length && file[dir.length] === path.sep && file.startsWith(dir)
109 | }
110 |
111 | function configureDevelopmentPlugins(configurator: WebpackConfigurator) {
112 | const optimization = configurator.config.optimization!!
113 | if (parseInt(String(webpackVersion), 10) >= 5) {
114 | optimization.moduleIds = 'named'
115 | }
116 | else {
117 | optimization.namedModules = true
118 | }
119 |
120 | const plugins = configurator.plugins
121 | plugins.push(new DefinePlugin({
122 | __static: `"${path.join(configurator.projectDir, configurator.staticSourceDirectory).replace(/\\/g, "\\\\")}"`,
123 | "process.env.NODE_ENV": configurator.isProduction ? "\"production\"" : "\"development\""
124 | }))
125 |
126 | plugins.push(new HotModuleReplacementPlugin())
127 |
128 | if (configurator.hasDevDependency("webpack-build-notifier")) {
129 | const WebpackNotifierPlugin = require("webpack-build-notifier")
130 | plugins.push(new WebpackNotifierPlugin({
131 | title: `Webpack - ${configurator.type}`,
132 | suppressSuccess: "initial",
133 | sound: false,
134 | }))
135 | }
136 |
137 | if (configurator.hasDevDependency("webpack-notifier")) {
138 | const WebpackNotifierPlugin = require("webpack-notifier")
139 | plugins.push(new WebpackNotifierPlugin({title: `Webpack - ${configurator.type}`}))
140 | }
141 |
142 | // watch common code
143 | let commonSourceDir = configurator.commonSourceDirectory
144 | if (commonSourceDir.endsWith(path.sep + getDefaultRelativeSystemDependentCommonSource())) {
145 | // not src/common, because it is convenient to just put some code into src to use it
146 | commonSourceDir = path.dirname(commonSourceDir)
147 | }
148 |
149 | const alienSourceDir = configurator.getSourceDirectory(configurator.type === "main" ? "renderer" : "main")
150 | const sourceDir = configurator.getSourceDirectory(configurator.type)
151 | configurator.plugins.push(new WatchFilterPlugin(file => {
152 | if (sourceDir != null && isAncestor(file, sourceDir)) {
153 | return true
154 | }
155 | else if (file === commonSourceDir || isAncestor(file, commonSourceDir!!)) {
156 | return alienSourceDir == null || !isAncestor(file, alienSourceDir)
157 | }
158 | else {
159 | return false
160 | }
161 | }, require("debug")(`electron-webpack:watch-${configurator.type}`)))
162 | }
163 |
--------------------------------------------------------------------------------
/packages/electron-webpack/src/dev/dev-runner.ts:
--------------------------------------------------------------------------------
1 | import * as BluebirdPromise from "bluebird"
2 | import chalk from "chalk"
3 | import { spawn } from "child_process"
4 | import { readdir, remove } from "fs-extra"
5 | import * as path from "path"
6 | import "source-map-support/register"
7 | import webpack, { Compiler } from "webpack"
8 | import { getElectronWebpackConfiguration, getPackageMetadata } from "../config"
9 | import { HmrServer } from "../electron-main-hmr/HmrServer"
10 | import { configure } from "../main"
11 | import { getFreePort, orNullIfFileNotExist } from "../util"
12 | import { DelayedFunction, getCommonEnv, logError, logProcess, logProcessErrorOutput } from "./devUtil"
13 | import { startRenderer } from "./WebpackDevServerManager"
14 |
15 | const projectDir = process.cwd()
16 |
17 | let socketPath: string | null = null
18 |
19 | const debug = require("debug")("electron-webpack")
20 |
21 | async function getOutDir() {
22 | const electronWebpackConfig = await getElectronWebpackConfiguration({
23 | projectDir,
24 | packageMetadata: getPackageMetadata(projectDir),
25 | })
26 | return path.join(electronWebpackConfig.commonDistDirectory!!, "main")
27 | }
28 |
29 | // do not remove main.js to allow IDE to keep breakpoints
30 | async function emptyMainOutput() {
31 | const outDir = await getOutDir()
32 | const files = await orNullIfFileNotExist(readdir(outDir))
33 | if (files == null) {
34 | return
35 | }
36 |
37 | await BluebirdPromise.map(files.filter(it => !it.startsWith(".") && it !== "main.js"), it => remove(outDir + path.sep + it))
38 | }
39 |
40 | class DevRunner {
41 | async start() {
42 | const wdsHost = "localhost"
43 | const wdsPort = await getFreePort(wdsHost, 9080)
44 | const env = {
45 | ...getCommonEnv(),
46 | ELECTRON_WEBPACK_WDS_HOST: wdsHost,
47 | ELECTRON_WEBPACK_WDS_PORT: wdsPort,
48 | }
49 |
50 | const hmrServer = new HmrServer()
51 | await Promise.all([
52 | startRenderer(projectDir, env),
53 | hmrServer.listen()
54 | .then(it => {
55 | socketPath = it
56 | }),
57 | emptyMainOutput()
58 | .then(() => this.startMainCompilation(hmrServer)),
59 | ])
60 |
61 | hmrServer.ipc.on("error", (error: Error) => {
62 | logError("Main", error)
63 | })
64 |
65 | const electronArgs = process.env.ELECTRON_ARGS
66 | const outDir = await getOutDir()
67 | const args = electronArgs != null && electronArgs.length > 0 ? JSON.parse(electronArgs) : [`--inspect=${await getFreePort("127.0.0.1", 5858)}`]
68 | args.push(path.join(outDir, "/main.js"))
69 | // Pass remaining arguments to the application. Remove 3 instead of 2, to remove the `dev` argument as well.
70 | args.push(...process.argv.slice(3))
71 | // we should start only when both start and main are started
72 | startElectron(args, env)
73 | }
74 |
75 | async startMainCompilation(hmrServer: HmrServer) {
76 | const mainConfig = await configure("main", {
77 | production: false,
78 | autoClean: false,
79 | forkTsCheckerLogger: {
80 | info: () => {
81 | // ignore
82 | },
83 |
84 | warn: (message: string) => {
85 | logProcess("Main", message, chalk.yellow)
86 | },
87 |
88 | error: (message: string) => {
89 | logProcess("Main", message, chalk.red)
90 | },
91 | },
92 | })
93 |
94 | await new Promise((resolve: (() => void) | null, reject: ((error: Error) => void) | null) => {
95 | const compiler: Compiler = webpack(mainConfig!!)
96 |
97 | const printCompilingMessage = new DelayedFunction(() => {
98 | logProcess("Main", "Compiling...", chalk.yellow)
99 | })
100 | compiler.hooks.compile.tap("electron-webpack-dev-runner", () => {
101 | hmrServer.beforeCompile()
102 | printCompilingMessage.schedule()
103 | })
104 |
105 | let watcher: Compiler.Watching | null = compiler.watch({}, (error, stats) => {
106 | printCompilingMessage.cancel()
107 |
108 | if (watcher == null) {
109 | return
110 | }
111 |
112 | if (error != null) {
113 | if (reject == null) {
114 | logError("Main", error)
115 | }
116 | else {
117 | reject(error)
118 | reject = null
119 | }
120 | return
121 | }
122 |
123 | logProcess("Main", stats.toString({
124 | colors: true,
125 | }), chalk.yellow)
126 |
127 | if (resolve != null) {
128 | resolve()
129 | resolve = null
130 | return
131 | }
132 |
133 | hmrServer.built(stats)
134 | })
135 |
136 | require("async-exit-hook")((callback: () => void) => {
137 | debug(`async-exit-hook: ${callback == null}`)
138 | const w = watcher
139 | if (w == null) {
140 | return
141 | }
142 |
143 | watcher = null
144 | w.close(() => callback())
145 | })
146 | })
147 | }
148 | }
149 |
150 | async function main() {
151 | const devRunner = new DevRunner()
152 | await devRunner.start()
153 | }
154 |
155 | main()
156 | .catch(error => {
157 | console.error(error)
158 | })
159 |
160 | function startElectron(electronArgs: Array, env: any) {
161 | const electronProcess = spawn(require("electron").toString(), electronArgs, {
162 | env: {
163 | ...env,
164 | ELECTRON_HMR_SOCKET_PATH: socketPath,
165 | }
166 | })
167 |
168 | // required on windows
169 | require("async-exit-hook")(() => {
170 | electronProcess.kill("SIGINT")
171 | })
172 |
173 | let queuedData: string | null = null
174 | electronProcess.stdout.on("data", data => {
175 | data = data.toString()
176 | // do not print the only line - doesn't make sense
177 | if (data.trim() === "[HMR] Updated modules:") {
178 | queuedData = data
179 | return
180 | }
181 |
182 | if (queuedData != null) {
183 | data = queuedData + data
184 | queuedData = null
185 | }
186 |
187 | logProcess("Electron", data, chalk.blue)
188 | })
189 |
190 | logProcessErrorOutput("Electron", electronProcess)
191 |
192 | electronProcess.on("close", exitCode => {
193 | debug(`Electron exited with exit code ${exitCode}`)
194 | if (exitCode === 100) {
195 | setImmediate(() => {
196 | startElectron(electronArgs, env)
197 | })
198 | }
199 | else {
200 | (process as any).emit("message", "shutdown")
201 | }
202 | })
203 | }
204 |
--------------------------------------------------------------------------------
/packages/electron-webpack/src/targets/RendererTarget.ts:
--------------------------------------------------------------------------------
1 | import { outputFile, readFile } from "fs-extra"
2 | import { Lazy } from "lazy-val"
3 | import * as path from "path"
4 | import { getConfig } from "read-config-file"
5 | import { DefinePlugin } from "webpack"
6 | import { getDllAssets } from "../configurators/dll"
7 | import { configureVueRenderer } from "../configurators/vue/vue"
8 | import { WebpackConfigurator } from "../main"
9 | import { statOrNull } from "../util"
10 | import { BaseTarget, configureFileLoader } from "./BaseTarget"
11 |
12 | const MiniCssExtractPlugin = require("mini-css-extract-plugin")
13 |
14 | export class BaseRendererTarget extends BaseTarget {
15 | constructor() {
16 | super()
17 | }
18 |
19 | configureRules(configurator: WebpackConfigurator): void {
20 | super.configureRules(configurator)
21 |
22 | configurator.extensions.push(".css")
23 |
24 | const miniLoaders = [MiniCssExtractPlugin.loader, { loader: "css-loader", options: { modules: "global" } }]
25 | const cssHotLoader = configurator.isProduction ? miniLoaders : ["css-hot-loader"].concat(miniLoaders)
26 | if (!configurator.isProduction) {
27 | // https://github.com/shepherdwind/css-hot-loader/issues/37
28 | configurator.entryFiles.unshift("css-hot-loader/hotModuleReplacement")
29 | }
30 |
31 | configurator.rules.push(
32 | {
33 | test: /\.css$/,
34 | use: cssHotLoader,
35 | },
36 | {
37 | test: /\.less$/,
38 | use: cssHotLoader.concat("less-loader"),
39 | },
40 | {
41 | test: /\.s([ac])ss$/,
42 | use: cssHotLoader.concat("sass-loader"),
43 | },
44 | {
45 | test: /\.(png|jpe?g|gif)(\?.*)?$/,
46 | use: {
47 | loader: "url-loader",
48 | options: configureFileLoader("imgs")
49 | }
50 | },
51 | {
52 | test: /\.svg$/,
53 | use: [
54 | {
55 | loader: '@svgr/webpack',
56 | },
57 | {
58 | loader: "url-loader",
59 | options: configureFileLoader("imgs")
60 | }
61 | ]
62 | },
63 | {
64 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
65 | loader: "url-loader",
66 | options: configureFileLoader("media"),
67 | },
68 | {
69 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
70 | use: {
71 | loader: "url-loader",
72 | options: configureFileLoader("fonts")
73 | }
74 | },
75 | )
76 |
77 | if (configurator.hasDevDependency("ejs-html-loader")) {
78 | configurator.rules.push({
79 | test: /\.ejs$/,
80 | loader: "ejs-html-loader",
81 | })
82 | }
83 |
84 | if (configurator.hasDependency("vue")) {
85 | configureVueRenderer(configurator)
86 | }
87 | else {
88 | configurator.rules.push({
89 | test: /\.(html)$/,
90 | use: {
91 | loader: "html-loader",
92 | }
93 | })
94 | }
95 | }
96 |
97 | async configurePlugins(configurator: WebpackConfigurator): Promise {
98 | configurator.debug("Add ExtractTextPlugin plugin")
99 | configurator.plugins.push(new MiniCssExtractPlugin({filename: `${configurator.type === "renderer-dll" ? "vendor" : "styles"}.css`}))
100 |
101 | await BaseTarget.prototype.configurePlugins.call(this, configurator)
102 | }
103 | }
104 |
105 | export class RendererTarget extends BaseRendererTarget {
106 | constructor() {
107 | super()
108 | }
109 |
110 | async configurePlugins(configurator: WebpackConfigurator): Promise {
111 | // not configurable for now, as in the electron-vue
112 | const customTemplateFile = path.join(configurator.projectDir, configurator.rendererTemplate)
113 | const HtmlWebpackPlugin = require("html-webpack-plugin")
114 | const nodeModulePath = configurator.isProduction ? null : path.resolve(require.resolve("electron"), "..", "..")
115 |
116 | let template
117 | if (await statOrNull(customTemplateFile)) {
118 | template = await readFile(customTemplateFile, {encoding: "utf8"})
119 | }
120 | else {
121 | template = getDefaultIndexTemplate()
122 | }
123 |
124 | configurator.plugins.push(new HtmlWebpackPlugin({
125 | filename: "index.html",
126 | template: await generateIndexFile(configurator, nodeModulePath, template),
127 | minify: false,
128 | nodeModules: nodeModulePath
129 | }))
130 |
131 | if (configurator.isProduction) {
132 | configurator.plugins.push(new DefinePlugin({
133 | __static: `process.resourcesPath + "/${configurator.staticSourceDirectory}"`
134 | }))
135 | }
136 | else {
137 | const contentBase = [path.join(configurator.projectDir, configurator.staticSourceDirectory), path.join(configurator.commonDistDirectory, "renderer-dll")];
138 | (configurator.config as any).devServer = {
139 | contentBase,
140 | host: process.env.ELECTRON_WEBPACK_WDS_HOST || "localhost",
141 | port: process.env.ELECTRON_WEBPACK_WDS_PORT || 9080,
142 | hot: true,
143 | overlay: true,
144 | }
145 | }
146 |
147 | await BaseRendererTarget.prototype.configurePlugins.call(this, configurator)
148 | }
149 | }
150 |
151 | async function computeTitle(configurator: WebpackConfigurator): Promise {
152 | const titleFromOptions = configurator.electronWebpackConfiguration.title
153 | if (titleFromOptions == null || titleFromOptions === false) {
154 | return null
155 | }
156 |
157 | if (titleFromOptions !== true) {
158 | return titleFromOptions
159 | }
160 |
161 | let title: string | null | undefined = (configurator.metadata as any).productName
162 | if (title == null) {
163 | const electronBuilderConfig = await getConfig({
164 | packageKey: "build",
165 | configFilename: "electron-builder",
166 | projectDir: configurator.projectDir,
167 | packageMetadata: new Lazy(() => Promise.resolve(configurator.metadata))
168 | })
169 | if (electronBuilderConfig != null) {
170 | title = electronBuilderConfig.result.productName
171 | }
172 | }
173 |
174 | if (title == null) {
175 | title = configurator.metadata.name
176 | }
177 | return title
178 | }
179 |
180 | function getDefaultIndexTemplate() {
181 | return `
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 | `
190 | }
191 |
192 | async function generateIndexFile(configurator: WebpackConfigurator, nodeModulePath: string | null, template: string) {
193 | // do not use add-asset-html-webpack-plugin - no need to copy vendor files to output (in dev mode will be served directly, in production copied)
194 | const assets = await getDllAssets(path.join(configurator.commonDistDirectory, "renderer-dll"), configurator)
195 | const scripts: Array = []
196 | const css: Array = []
197 | for (const asset of assets) {
198 | if (asset.endsWith(".js")) {
199 | scripts.push(``)
200 | }
201 | else {
202 | css.push(``)
203 | }
204 | }
205 |
206 | const title = await computeTitle(configurator)
207 | const filePath = path.join(configurator.commonDistDirectory, ".renderer-index-template.html")
208 |
209 | let html = template
210 | if (title) {
211 | html = html.replace("", `${title}`)
212 | }
213 |
214 | if (nodeModulePath) {
215 | html = html.replace("", ``)
216 | }
217 |
218 | html = html.replace("", '')
219 |
220 | if (scripts.length) {
221 | html = html.replace("", `${scripts.join("")}`)
222 | }
223 |
224 | if (css.length) {
225 | html = html.replace("", `${css.join("")}`)
226 | }
227 |
228 | await outputFile(filePath, html)
229 |
230 | return `!!html-loader?minimize=false&attributes=false!${filePath}`
231 | }
232 |
--------------------------------------------------------------------------------
/packages/electron-webpack/typings/yargs.d.ts:
--------------------------------------------------------------------------------
1 | declare namespace yargs {
2 | interface Yargs {
3 | argv: any;
4 |
5 | (...args: any[]): any;
6 |
7 | parse(...args: any[]): any;
8 |
9 | reset(): Yargs;
10 |
11 | locale(): string;
12 |
13 | locale(loc: string): Yargs;
14 |
15 | detectLocale(detect: boolean): Yargs;
16 |
17 | terminalWidth(): number;
18 |
19 | alias(shortName: string, longName: string): Yargs;
20 |
21 | alias(aliases: { [shortName: string]: string }): Yargs;
22 |
23 | alias(aliases: { [shortName: string]: string[] }): Yargs;
24 |
25 | array(key: string): Yargs;
26 |
27 | array(keys: string[]): Yargs;
28 |
29 | default(key: string, value: any, description?: string): Yargs;
30 |
31 | default(defaults: { [key: string]: any }, description?: string): Yargs;
32 |
33 | /**
34 | * @deprecated since version 6.6.0
35 | */
36 | demand(key: string, msg: string): Yargs;
37 |
38 | demand(key: string, required?: boolean): Yargs;
39 |
40 | demand(keys: string[], msg: string): Yargs;
41 |
42 | demand(keys: string[], required?: boolean): Yargs;
43 |
44 | demand(positionals: number, required?: boolean): Yargs;
45 |
46 | demand(positionals: number, msg: string): Yargs;
47 |
48 | demand(positionals: number, max: number, msg?: string): Yargs;
49 |
50 | demandCommand(min: number, minMsg?: string): Yargs;
51 |
52 | demandCommand(min: number, max?: number, minMsg?: string, maxMsg?: string): Yargs;
53 |
54 | demandOption(key: string | string[], msg?: string): Yargs;
55 |
56 | demandOption(key: string | string[], demand?: boolean): Yargs;
57 |
58 | /**
59 | * @deprecated since version 6.6.0
60 | */
61 | require(key: string, msg: string): Yargs;
62 |
63 | require(key: string, required: boolean): Yargs;
64 |
65 | require(keys: number[], msg: string): Yargs;
66 |
67 | require(keys: number[], required: boolean): Yargs;
68 |
69 | require(positionals: number, required: boolean): Yargs;
70 |
71 | require(positionals: number, msg: string): Yargs;
72 |
73 | /**
74 | * @deprecated since version 6.6.0
75 | */
76 | required(key: string, msg: string): Yargs;
77 |
78 | required(key: string, required: boolean): Yargs;
79 |
80 | required(keys: number[], msg: string): Yargs;
81 |
82 | required(keys: number[], required: boolean): Yargs;
83 |
84 | required(positionals: number, required: boolean): Yargs;
85 |
86 | required(positionals: number, msg: string): Yargs;
87 |
88 | requiresArg(key: string): Yargs;
89 |
90 | requiresArg(keys: string[]): Yargs;
91 |
92 | describe(key: string, description: string): Yargs;
93 |
94 | describe(descriptions: { [key: string]: string }): Yargs;
95 |
96 | option(key: string, options: Options): Yargs;
97 |
98 | option(options: { [key: string]: Options }): Yargs;
99 |
100 | options(key: string, options: Options): Yargs;
101 |
102 | options(options: { [key: string]: Options }): Yargs;
103 |
104 | usage(message: string, options?: { [key: string]: Options }): Yargs;
105 |
106 | usage(options?: { [key: string]: Options }): Yargs;
107 |
108 | command(command: string, description: string): Yargs;
109 |
110 | command(command: string | Array, description: string, builder: (args: Yargs) => Yargs): Yargs;
111 |
112 | command(command: string | Array, description: string, builder: { [optionName: string]: Options }): Yargs;
113 |
114 | // command(command: string, description: string, builder: { [optionName: string]: Options }, handler: (args: any) => void): Yargs;
115 |
116 | command(command: string | Array, description: string, builder: (args: Yargs) => Yargs, handler: (args: any) => void): Yargs;
117 |
118 | command(command: string | Array, description: string, module: CommandModule): Yargs;
119 |
120 | command(module: CommandModule): Yargs;
121 |
122 | commandDir(dir: string, opts?: RequireDirectoryOptions): Yargs;
123 |
124 | completion(): Yargs;
125 |
126 | completion(cmd: string, fn?: AsyncCompletionFunction): Yargs;
127 |
128 | completion(cmd: string, fn?: SyncCompletionFunction): Yargs;
129 |
130 | completion(cmd: string | undefined, description?: string, fn?: AsyncCompletionFunction): Yargs;
131 |
132 | completion(cmd: string, description?: string, fn?: SyncCompletionFunction): Yargs;
133 |
134 | example(command: string, description: string): Yargs;
135 |
136 | check(func: (Yargs: any, aliases: { [alias: string]: string }) => any): Yargs;
137 |
138 | boolean(key: string): Yargs;
139 |
140 | boolean(keys: string[]): Yargs;
141 |
142 | string(key: string): Yargs;
143 |
144 | string(keys: string[]): Yargs;
145 |
146 | number(key: string): Yargs;
147 |
148 | number(keys: string[]): Yargs;
149 |
150 | choices(choices: Object): Yargs;
151 |
152 | choices(key: string, values: any[]): Yargs;
153 |
154 | config(): Yargs;
155 |
156 | config(explicitConfigurationObject: Object): Yargs;
157 |
158 | config(key: string, description?: string, parseFn?: (configPath: string) => Object): Yargs;
159 |
160 | config(keys: string[], description?: string, parseFn?: (configPath: string) => Object): Yargs;
161 |
162 | config(key: string, parseFn: (configPath: string) => Object): Yargs;
163 |
164 | config(keys: string[], parseFn: (configPath: string) => Object): Yargs;
165 |
166 | conflicts(key: string, value: string): Yargs;
167 |
168 | conflicts(conflicts: { [key: string]: string }): Yargs;
169 |
170 | wrap(columns: number): Yargs;
171 |
172 | strict(): Yargs;
173 |
174 | help(): Yargs;
175 |
176 | help(enableExplicit: boolean): Yargs;
177 |
178 | help(option: string, enableExplicit: boolean): Yargs;
179 |
180 | help(option: string, description?: string, enableExplicit?: boolean): Yargs;
181 |
182 | env(prefix?: string): Yargs;
183 |
184 | env(enable: boolean): Yargs;
185 |
186 | epilog(msg: string): Yargs;
187 |
188 | epilogue(msg: string): Yargs;
189 |
190 | version(version?: string, option?: string, description?: string): Yargs;
191 |
192 | version(version: () => string, option?: string, description?: string): Yargs;
193 |
194 | showHelpOnFail(enable: boolean, message?: string): Yargs;
195 |
196 | showHelp(consoleLevel?: string): Yargs;
197 |
198 | exitProcess(enabled: boolean): Yargs;
199 |
200 | global(key: string): Yargs;
201 |
202 | global(keys: string[]): Yargs;
203 |
204 | group(key: string, groupName: string): Yargs;
205 |
206 | group(keys: string[], groupName: string): Yargs;
207 |
208 | nargs(key: string, count: number): Yargs;
209 |
210 | nargs(nargs: { [key: string]: number }): Yargs;
211 |
212 | normalize(key: string): Yargs;
213 |
214 | normalize(keys: string[]): Yargs;
215 |
216 | implies(key: string, value: string): Yargs;
217 |
218 | implies(implies: { [key: string]: string }): Yargs;
219 |
220 | count(key: string): Yargs;
221 |
222 | count(keys: string[]): Yargs;
223 |
224 | fail(func: (msg: string, err: Error) => any): Yargs;
225 |
226 | coerce(key: string | string[], func: (arg: T) => U): Yargs;
227 |
228 | coerce(opts: { [key: string]: (arg: T) => U; }): Yargs;
229 |
230 | getCompletion(args: string[], done: (completions: string[]) => void): Yargs;
231 |
232 | pkgConf(key: string, cwd?: string): Yargs;
233 |
234 | pkgConf(keys: string[], cwd?: string): Yargs;
235 |
236 | recommendCommands(): Yargs;
237 |
238 | showCompletionScript(): Yargs;
239 |
240 | skipValidation(key: string): Yargs;
241 |
242 | skipValidation(keys: string[]): Yargs;
243 |
244 | updateLocale(obj: Object): Yargs;
245 |
246 | updateStrings(obj: { [key: string]: string }): Yargs;
247 | }
248 |
249 | interface RequireDirectoryOptions {
250 | recurse?: boolean;
251 | extensions?: string[];
252 | visit?: (commandObject: any, pathToFile?: string, filename?: string) => any;
253 | include?: RegExp | ((pathToFile: string) => boolean);
254 | exclude?: RegExp | ((pathToFile: string) => boolean);
255 | }
256 |
257 | interface Options {
258 | alias?: string | string[];
259 | array?: boolean;
260 | boolean?: boolean;
261 | choices?: string[];
262 | coerce?: (arg: any) => any;
263 | config?: boolean;
264 | configParser?: (configPath: string) => Object;
265 | count?: boolean;
266 | default?: any;
267 | defaultDescription?: string;
268 | /** @deprecated since version 6.6.0 */
269 | demand?: boolean | string;
270 | demandOption?: boolean | string;
271 | desc?: string;
272 | describe?: string;
273 | description?: string;
274 | global?: boolean;
275 | group?: string;
276 | nargs?: number;
277 | normalize?: boolean;
278 | number?: boolean;
279 | require?: boolean | string;
280 | required?: boolean | string;
281 | requiresArg?: boolean | string;
282 | skipValidation?: boolean;
283 | string?: boolean;
284 | type?: "array" | "boolean" | "count" | "number" | "string";
285 | }
286 |
287 | interface CommandModule {
288 | aliases?: string[] | string;
289 | builder?: CommandBuilder;
290 | command?: string[] | string;
291 | describe?: string | false;
292 | handler: (args: any) => void;
293 | }
294 |
295 | type CommandBuilder = { [key: string]: Options } | ((args: Yargs) => Yargs);
296 | type SyncCompletionFunction = (current: string, Yargs: any) => string[];
297 | type AsyncCompletionFunction = (current: string, Yargs: any, done: (completion: string[]) => void) => void;
298 | }
299 |
300 | declare module "yargs" {
301 | const yargs: yargs.Yargs;
302 | export default yargs;
303 | }
304 |
--------------------------------------------------------------------------------
/packages/electron-webpack/src/main.ts:
--------------------------------------------------------------------------------
1 | import * as BluebirdPromise from "bluebird"
2 | import { config as dotEnvConfig } from "dotenv"
3 | import dotEnvExpand from "dotenv-expand"
4 | import { pathExists, readJson } from "fs-extra"
5 | import { Lazy } from "lazy-val"
6 | import * as path from "path"
7 | import { validateConfig } from "read-config-file"
8 | import { deepAssign } from "read-config-file/out/deepAssign"
9 | import "source-map-support/register"
10 | import { Configuration, Plugin, RuleSetRule } from "webpack"
11 | import merge from "webpack-merge"
12 | import { getElectronWebpackConfiguration, getPackageMetadata } from "./config"
13 | import { configureTypescript } from "./configurators/ts"
14 | import { configureVue } from "./configurators/vue/vue"
15 | import { ConfigurationEnv, ConfigurationType, ElectronWebpackConfiguration, PackageMetadata, PartConfiguration } from "./core"
16 | import { BaseTarget } from "./targets/BaseTarget"
17 | import { MainTarget } from "./targets/MainTarget"
18 | import { BaseRendererTarget, RendererTarget } from "./targets/RendererTarget"
19 | import { getFirstExistingFile } from "./util"
20 |
21 | export { ElectronWebpackConfiguration } from "./core"
22 |
23 | const _debug = require("debug")
24 |
25 | // noinspection JSUnusedGlobalSymbols
26 | export function getAppConfiguration(env: ConfigurationEnv) {
27 | return BluebirdPromise.filter([configure("main", env), configure("renderer", env)], it => it != null)
28 | }
29 |
30 | // noinspection JSUnusedGlobalSymbols
31 | export function getMainConfiguration(env: ConfigurationEnv) {
32 | return configure("main", env)
33 | }
34 |
35 | // noinspection JSUnusedGlobalSymbols
36 | export function getRendererConfiguration(env: ConfigurationEnv) {
37 | return configure("renderer", env)
38 | }
39 |
40 | // in the future, if need, isRenderer = true arg can be added
41 | // noinspection JSUnusedGlobalSymbols
42 | export function getDllConfiguration(env: ConfigurationEnv) {
43 | return configure("renderer-dll", env)
44 | }
45 |
46 | // noinspection JSUnusedGlobalSymbols
47 | export async function getTestConfiguration(env: ConfigurationEnv) {
48 | const configurator = await createConfigurator("test", env)
49 | return await configurator.configure({
50 | testComponents: path.join(process.cwd(), "src/renderer/components/testComponents.ts"),
51 | })
52 | }
53 |
54 | export class WebpackConfigurator {
55 | readonly projectDir: string
56 |
57 | private electronVersionPromise = new Lazy(() => getInstalledElectronVersion(this.projectDir))
58 |
59 | readonly isRenderer: boolean
60 | readonly isProduction: boolean
61 | readonly isTest = this.type === "test"
62 |
63 | readonly sourceDir: string
64 | readonly staticSourceDirectory: string
65 | readonly commonSourceDirectory: string
66 | readonly commonDistDirectory: string
67 |
68 | readonly rendererTemplate: string
69 |
70 | readonly debug = _debug(`electron-webpack:${this.type}`)
71 |
72 | private _configuration: Configuration | null = null
73 |
74 | get config(): Configuration {
75 | return this._configuration!!
76 | }
77 |
78 | readonly rules: Array = []
79 | readonly plugins: Array = []
80 |
81 | // js must be first - e.g. iView has two files loading-bar.js and loading-bar.vue - when we require "loading-bar", js file must be resolved and not vue
82 | readonly extensions: Array = [".js", ".json", ".node"]
83 |
84 | private _electronVersion: string | null = null
85 |
86 | get electronVersion(): string {
87 | return this._electronVersion!!
88 | }
89 |
90 | readonly entryFiles: Array = []
91 |
92 | // electronWebpackConfiguration expected to be resolved (use getElectronWebpackConfiguration())
93 | constructor(readonly type: ConfigurationType, readonly env: ConfigurationEnv, readonly electronWebpackConfiguration: ElectronWebpackConfiguration, readonly metadata: PackageMetadata) {
94 | if (metadata.dependencies == null) {
95 | metadata.dependencies = {}
96 | }
97 | if (metadata.devDependencies == null) {
98 | metadata.devDependencies = {}
99 | }
100 |
101 | this.projectDir = electronWebpackConfiguration.projectDir!!
102 | this.isRenderer = type.startsWith("renderer")
103 | process.env.BABEL_ENV = type
104 |
105 | this.isProduction = this.env.production == null ? process.env.NODE_ENV === "production" : this.env.production
106 |
107 | this.debug(`isProduction: ${this.isProduction}`)
108 |
109 | this.sourceDir = this.getSourceDirectory(this.type)!!
110 |
111 | this.staticSourceDirectory = this.electronWebpackConfiguration.staticSourceDirectory!!
112 | this.commonSourceDirectory = this.electronWebpackConfiguration.commonSourceDirectory!!
113 | this.commonDistDirectory = this.electronWebpackConfiguration.commonDistDirectory!!
114 |
115 | this.rendererTemplate = (this.electronWebpackConfiguration.renderer && this.electronWebpackConfiguration.renderer.template) || "src/index.ejs"
116 | }
117 |
118 | /**
119 | * Returns null if code processing for type is disabled.
120 | */
121 | getSourceDirectory(type: ConfigurationType): string | null {
122 | const part = this.getPartConfiguration(type)
123 | if (part === null || (part != null && part.sourceDirectory === null)) {
124 | // part or sourceDirectory is explicitly set to null
125 | return null
126 | }
127 |
128 | const result = part == null ? null : part.sourceDirectory
129 | if (result == null) {
130 | return path.join(this.projectDir, "src", type.startsWith("renderer") || type === "test" ? "renderer" : type)
131 | }
132 | else {
133 | return path.resolve(this.projectDir, result)
134 | }
135 | }
136 |
137 | getPartConfiguration(type: ConfigurationType): PartConfiguration | null | undefined {
138 | if (type === "main") {
139 | return this.electronWebpackConfiguration.main
140 | }
141 | else {
142 | return this.electronWebpackConfiguration.renderer
143 | }
144 | }
145 |
146 | hasDependency(name: string) {
147 | return name in this.metadata.dependencies || this.hasDevDependency(name)
148 | }
149 |
150 | hasDevDependency(name: string) {
151 | return name in this.metadata.devDependencies
152 | }
153 |
154 | /**
155 | * Returns the names of devDependencies that match a given string or regex.
156 | * If no matching dependencies are found, an empty array is returned.
157 | *
158 | * @return list of matching dependency names, e.g. `["@babel/preset-react", "@babel/preset-stage-0"]`
159 | */
160 | getMatchingDevDependencies(options: GetMatchingDevDependenciesOptions = {}) {
161 | const includes = options.includes || []
162 | const excludes = new Set(options.excludes || [])
163 | return Object.keys(this.metadata.devDependencies)
164 | .filter(name => !excludes.has(name) && includes.some(prefix => name.startsWith(prefix)))
165 | }
166 |
167 | async configure(entry?: { [key: string]: any } | null) {
168 | // noinspection SpellCheckingInspection
169 | this._configuration = {
170 | context: this.projectDir,
171 | devtool: this.isProduction || this.isTest ? "nosources-source-map" : "eval-source-map",
172 | externals: this.computeExternals(),
173 | node: {
174 | __dirname: !this.isProduction,
175 | __filename: !this.isProduction,
176 | },
177 | output: {
178 | filename: "[name].js",
179 | chunkFilename: "[name].bundle.js",
180 | libraryTarget: "commonjs2",
181 | path: path.join(this.commonDistDirectory, this.type)
182 | },
183 | target: this.isTest ? "node" : `electron-${this.type === "renderer-dll" ? "renderer" : this.type}` as any,
184 | resolve: {
185 | alias: {
186 | "@": this.sourceDir,
187 | common: this.commonSourceDirectory,
188 | },
189 | extensions: this.extensions,
190 | },
191 | module: {
192 | rules: this.rules,
193 | },
194 | plugins: this.plugins,
195 | }
196 |
197 | if (entry != null) {
198 | this._configuration.entry = entry
199 | }
200 |
201 | // if electronVersion not specified, use latest
202 | this._electronVersion = this.electronWebpackConfiguration.electronVersion || await this.electronVersionPromise.value || "3.0.7"
203 | const target = (() => {
204 | switch (this.type) {
205 | case "renderer": return new RendererTarget()
206 | case "renderer-dll": return new BaseRendererTarget()
207 | case "test": return new BaseRendererTarget()
208 | case "main": return new MainTarget()
209 | default: return new BaseTarget()
210 | }
211 | })()
212 | this.debug(`Target class: ${target.constructor.name}`)
213 | target.configureRules(this)
214 | await Promise.all([target.configurePlugins(this), configureTypescript(this)])
215 | configureVue(this)
216 |
217 | if (this.debug.enabled) {
218 | this.debug(`\n\n${this.type} config:` + JSON.stringify(this._configuration, null, 2) + "\n\n")
219 | }
220 |
221 | if (this.config.entry == null) {
222 | this.entryFiles.push((await computeEntryFile(this.sourceDir, this.projectDir))!!)
223 | this.config.entry = {
224 | [this.type]: this.entryFiles,
225 | }
226 |
227 | const mainConfiguration = this.electronWebpackConfiguration.main || {}
228 | let extraEntries = mainConfiguration.extraEntries
229 | if (this.type === "main" && extraEntries != null) {
230 | if (typeof extraEntries === "string") {
231 | extraEntries = [extraEntries]
232 | }
233 |
234 | if (Array.isArray(extraEntries)) {
235 | for (const p of extraEntries) {
236 | this.config.entry[path.basename(p, path.extname(p))] = p
237 | }
238 | }
239 | else {
240 | Object.assign(this.config.entry, extraEntries)
241 | }
242 | }
243 | }
244 |
245 | // noinspection ES6RedundantAwait
246 | this._configuration = await Promise.resolve(this.applyCustomModifications(this.config))
247 |
248 | return this.config
249 | }
250 |
251 | private applyCustomModifications(config: Configuration): Configuration {
252 | const { renderer, main } = this.electronWebpackConfiguration
253 |
254 | const applyCustom = (configPath: string) => {
255 | const customModule = require(path.join(this.projectDir, configPath))
256 | if (typeof customModule === "function") {
257 | return customModule(config, this)
258 | }
259 | else {
260 | return merge.smart(config, customModule)
261 | }
262 | }
263 |
264 | if (this.type === "renderer" && renderer && renderer.webpackConfig) {
265 | return applyCustom(renderer.webpackConfig)
266 | }
267 | else if (this.type === "renderer-dll" && renderer && renderer.webpackDllConfig) {
268 | return applyCustom(renderer.webpackDllConfig)
269 | }
270 | else if (this.type === "main" && main && main.webpackConfig) {
271 | return applyCustom(main.webpackConfig)
272 | }
273 | else {
274 | return config
275 | }
276 | }
277 |
278 | private computeExternals() {
279 | const whiteListedModules = new Set(this.electronWebpackConfiguration.whiteListedModules || [])
280 | if (this.isRenderer) {
281 | whiteListedModules.add("react")
282 | whiteListedModules.add("react-dom")
283 | whiteListedModules.add("vue")
284 | }
285 |
286 | const filter = (name: string) => !name.startsWith("@types/") && (whiteListedModules == null || !whiteListedModules.has(name))
287 | const externals: Array = Object.keys(this.metadata.dependencies).filter(filter)
288 | externals.push("electron")
289 | externals.push("webpack")
290 | // because electron-devtools-installer specified in the devDependencies, but required in the index.dev
291 | externals.push("electron-devtools-installer")
292 | if (this.type === "main") {
293 | externals.push("webpack/hot/log-apply-result")
294 | externals.push("electron-webpack/out/electron-main-hmr/HmrClient")
295 | externals.push("source-map-support/source-map-support.js")
296 | }
297 |
298 | if (this.electronWebpackConfiguration.externals != null) {
299 | return externals.concat(this.electronWebpackConfiguration.externals)
300 | }
301 |
302 | return externals
303 | }
304 | }
305 |
306 | const schemeDataPromise = new Lazy(() => readJson(path.join(__dirname, "..", "scheme.json")))
307 |
308 | export async function createConfigurator(type: ConfigurationType, env: ConfigurationEnv | null) {
309 | if (env != null) {
310 | // allow to pass as `--env.autoClean=false` webpack arg
311 | const _env: any = env
312 | for (const name of ["minify", "autoClean", "production"]) {
313 | if (_env[name] === "true") {
314 | _env[name] = true
315 | }
316 | else if (_env[name] === "false") {
317 | _env[name] = false
318 | }
319 | }
320 | }
321 |
322 | if (env == null) {
323 | env = {}
324 | }
325 |
326 | const projectDir = (env.configuration || {}).projectDir || process.cwd()
327 | const packageMetadata = getPackageMetadata(projectDir)
328 | const electronWebpackConfig = await getElectronWebpackConfiguration({
329 | packageMetadata,
330 | projectDir,
331 | })
332 | if (env.configuration != null) {
333 | deepAssign(electronWebpackConfig, env.configuration)
334 | }
335 |
336 | await validateConfig(electronWebpackConfig, schemeDataPromise, message => {
337 | return `${message}
338 |
339 | How to fix:
340 | 1. Open https://webpack.electron.build/configuration
341 | 2. Search the option name on the page.
342 | * Not found? The option was deprecated or not exists (check spelling).
343 | * Found? Check that the option in the appropriate place. e.g. "sourceDirectory" only in the "main" or "renderer", not in the root.
344 | `
345 | })
346 | return new WebpackConfigurator(type, env, electronWebpackConfig, await packageMetadata.value)
347 | }
348 |
349 | export async function configure(type: ConfigurationType, env: ConfigurationEnv | null): Promise {
350 | const configurator = await createConfigurator(type, env)
351 | const sourceDir = configurator.sourceDir
352 | // explicitly set to null - do not handle at all and do not show info message
353 | if (sourceDir === null) {
354 | return null
355 | }
356 |
357 | const processEnv = configurator.isProduction ? "production" : "development"
358 | const dotEnvPath = path.resolve(configurator.projectDir, ".env")
359 | const dotenvFiles = [
360 | `${dotEnvPath}.${processEnv}.local`,
361 | `${dotEnvPath}.${processEnv}`,
362 | `${dotEnvPath}.local`,
363 | dotEnvPath,
364 | ]
365 |
366 | for (const file of dotenvFiles) {
367 | const exists = await pathExists(file)
368 | if (exists) {
369 | dotEnvExpand(
370 | dotEnvConfig({
371 | path: file
372 | })
373 | )
374 | }
375 | }
376 | return await configurator.configure()
377 | }
378 |
379 | async function computeEntryFile(srcDir: string, projectDir: string): Promise {
380 | const candidates: Array = []
381 | for (const ext of ["ts", "js", "tsx", "jsx"]) {
382 | for (const name of ["index", "main", "app"]) {
383 | candidates.push(`${name}.${ext}`)
384 | }
385 | }
386 |
387 | const file = await getFirstExistingFile(candidates, srcDir)
388 | if (file == null) {
389 | throw new Error(`Cannot find entry file ${path.relative(projectDir, path.join(srcDir, "index.ts"))} (or main.ts, or app.ts, or index.js, or main.js, or app.js)`)
390 | }
391 | return file
392 | }
393 |
394 | async function getInstalledElectronVersion(projectDir: string) {
395 | for (const name of ["electron", "electron-prebuilt", "electron-prebuilt-compile"]) {
396 | try {
397 | return (await readJson(path.join(projectDir, "node_modules", name, "package.json"))).version
398 | }
399 | catch (e) {
400 | if (e.code !== "ENOENT") {
401 | throw e
402 | }
403 | }
404 | }
405 | }
406 |
407 | export interface GetMatchingDevDependenciesOptions {
408 | /**
409 | * The list of prefixes to include, e.g. `["babel-preset-"]`.
410 | */
411 | includes?: Array
412 | /**
413 | * The list of names to exclude.
414 | */
415 | excludes?: Array
416 | }
417 |
--------------------------------------------------------------------------------