Skip to content

Commit 7a794a1

Browse files
committed
feat: add support for webpack encore
1 parent 8c1c0c1 commit 7a794a1

File tree

7 files changed

+334
-27
lines changed

7 files changed

+334
-27
lines changed

commands/Build.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,29 @@ export default class Build extends BaseCommand {
2323
@flags.boolean({ description: 'Build for production', alias: 'prod' })
2424
public production: boolean
2525

26+
/**
27+
* Bundle frontend assets. Defaults to true
28+
*/
29+
@flags.boolean({
30+
description: 'Bundle frontend assets when encore is installed. Use --no-assets to disable',
31+
default: true,
32+
})
33+
public assets: boolean
34+
35+
/**
36+
* Ignore ts errors and complete the build process. Defaults to false
37+
*/
2638
@flags.boolean({
2739
description: 'Ignore typescript errors and complete the build process',
28-
alias: 'prod',
2940
})
3041
public ignoreTsErrors: boolean
3142

43+
/**
44+
* Arguments to pass to the `encore` binary
45+
*/
46+
@flags.array({ description: 'CLI options to pass to the encore command line' })
47+
public encoreArgs: string[] = []
48+
3249
/**
3350
* Select the client for deciding the lock file to copy to the
3451
* build folder
@@ -50,6 +67,7 @@ export default class Build extends BaseCommand {
5067
this.client = this.client || hasYarn(this.application.appRoot) ? 'yarn' : 'npm'
5168
if (this.client !== 'npm' && this.client !== 'yarn') {
5269
this.logger.warning('--client must be set to "npm" or "yarn"')
70+
this.exitCode = 1
5371
return
5472
}
5573

@@ -60,15 +78,23 @@ export default class Build extends BaseCommand {
6078

6179
try {
6280
if (this.production) {
63-
await new Compiler(this.application.appRoot, this.logger).compileForProduction(
64-
stopOnError,
65-
this.client
66-
)
81+
await new Compiler(
82+
this.application.appRoot,
83+
this.encoreArgs,
84+
this.assets,
85+
this.logger
86+
).compileForProduction(stopOnError, this.client)
6787
} else {
68-
await new Compiler(this.application.appRoot, this.logger).compile(stopOnError)
88+
await new Compiler(
89+
this.application.appRoot,
90+
this.encoreArgs,
91+
this.assets,
92+
this.logger
93+
).compile(stopOnError)
6994
}
7095
} catch (error) {
7196
this.logger.fatal(error)
97+
this.exitCode = 1
7298
}
7399
}
74100
}

commands/Serve.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ export default class Serve extends BaseCommand {
2121
stayAlive: true,
2222
}
2323

24+
/**
25+
* Bundle frontend assets. Defaults to true
26+
*/
27+
@flags.boolean({
28+
description: 'Start webpack dev server when encore is installed. Use --no-assets to disable',
29+
default: true,
30+
})
31+
public assets: boolean
32+
2433
/**
2534
* Allows watching for file changes
2635
*/
@@ -42,14 +51,32 @@ export default class Serve extends BaseCommand {
4251
@flags.array({ description: 'CLI options to pass to the node command line' })
4352
public nodeArgs: string[] = []
4453

54+
/**
55+
* Arguments to pass to the `encore` binary
56+
*/
57+
@flags.array({ description: 'CLI options to pass to the encore command line' })
58+
public encoreArgs: string[] = []
59+
4560
public async run() {
4661
const { DevServer } = await import('../src/DevServer')
4762

4863
try {
4964
if (this.watch) {
50-
await new DevServer(this.application.appRoot, this.nodeArgs, this.logger).watch(this.poll)
65+
await new DevServer(
66+
this.application.appRoot,
67+
this.nodeArgs,
68+
this.encoreArgs,
69+
this.assets,
70+
this.logger
71+
).watch(this.poll)
5172
} else {
52-
await new DevServer(this.application.appRoot, this.nodeArgs, this.logger).start()
73+
await new DevServer(
74+
this.application.appRoot,
75+
this.nodeArgs,
76+
this.encoreArgs,
77+
this.assets,
78+
this.logger
79+
).start()
5380
}
5481
} catch (error) {
5582
this.logger.fatal(error)

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@
4343
},
4444
"homepage": "https://github.com/adonisjs/assembler#readme",
4545
"devDependencies": {
46-
"@adonisjs/ace": "^9.0.2",
46+
"@adonisjs/ace": "^9.0.3",
4747
"@adonisjs/core": "^5.0.5-canary-rc-1",
4848
"@adonisjs/mrm-preset": "^3.0.0",
4949
"@poppinss/dev-utils": "^1.1.0",
50-
"@types/node": "^14.14.27",
50+
"@types/node": "^14.14.28",
5151
"commitizen": "^4.2.3",
5252
"copyfiles": "^2.4.1",
5353
"cz-conventional-changelog": "^3.3.0",

src/AssetsBundler/index.ts

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/*
2+
* @adonisjs/assembler
3+
*
4+
* (c) Harminder Virk <[email protected]>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
import execa from 'execa'
11+
import Emittery from 'emittery'
12+
import { logger as uiLogger } from '@poppinss/cliui'
13+
import { resolveDir } from '@poppinss/utils/build/helpers'
14+
15+
/**
16+
* Assets bundler uses webpack encore to build frontend dependencies
17+
*/
18+
export class AssetsBundler extends Emittery {
19+
/**
20+
* Binary to execute
21+
*/
22+
private binaryName = 'encore'
23+
24+
/**
25+
* Options passed to spawn a child process
26+
*/
27+
private execaOptions = {
28+
preferLocal: true,
29+
buffer: false,
30+
stdio: 'pipe' as const,
31+
localDir: this.projectRoot,
32+
cwd: this.projectRoot,
33+
env: {
34+
FORCE_COLOR: 'true',
35+
...this.env,
36+
},
37+
}
38+
39+
constructor(
40+
private projectRoot: string,
41+
private encoreArgs: string[] = [],
42+
private buildAssets: boolean = true,
43+
private logger: typeof uiLogger,
44+
private env: { [key: string]: string } = {}
45+
) {
46+
super()
47+
}
48+
49+
/**
50+
* Find if encore is installed
51+
*/
52+
private isEncoreInstalled() {
53+
try {
54+
resolveDir(this.projectRoot, '@symfony/webpack-encore')
55+
return true
56+
} catch {
57+
return false
58+
}
59+
}
60+
61+
/**
62+
* Notify user that we are about use encore
63+
*/
64+
private notifyAboutEncore() {
65+
this.logger.info(`detected { ${this.logger.colors.dim().yellow('@symfony/webpack-encore')} }`)
66+
this.logger.info(
67+
`building frontend assets. Use { ${this.logger.colors
68+
.dim()
69+
.yellow('--no-assets')} } to disable`
70+
)
71+
}
72+
73+
/**
74+
* Logs the line to stdout
75+
*/
76+
private log(line: Buffer | string) {
77+
line = line.toString().trim()
78+
if (!line.length) {
79+
return
80+
}
81+
console.log(`[ ${this.logger.colors.cyan('encore')} ] ${line}`)
82+
}
83+
84+
/**
85+
* Logs the line to stderr
86+
*/
87+
private logError(line: Buffer | string) {
88+
line = line.toString().trim()
89+
if (!line.length) {
90+
return
91+
}
92+
console.error(`[ ${this.logger.colors.cyan('encore')} ] ${line}`)
93+
}
94+
95+
/**
96+
* Execute command
97+
*/
98+
private exec(args: string[]): Promise<void> {
99+
return new Promise((resolve, reject) => {
100+
const childProcess = execa(this.binaryName, args, this.execaOptions)
101+
102+
childProcess.stdout?.on('data', (line: Buffer) => this.log(line))
103+
childProcess.stderr?.on('data', (line: Buffer) => this.logError(line))
104+
childProcess.on('error', (error) => reject(error))
105+
childProcess.on('close', (code) => {
106+
if (code && code !== 0) {
107+
reject(`Process exited with code ${code}`)
108+
} else {
109+
resolve()
110+
}
111+
})
112+
})
113+
}
114+
115+
/**
116+
* Build assets using encore
117+
*/
118+
public async build(): Promise<{ hasErrors: boolean }> {
119+
if (!this.buildAssets) {
120+
return { hasErrors: false }
121+
}
122+
123+
if (!this.isEncoreInstalled()) {
124+
return { hasErrors: false }
125+
}
126+
127+
this.notifyAboutEncore()
128+
129+
try {
130+
await this.exec(['dev'].concat(this.encoreArgs))
131+
return { hasErrors: false }
132+
} catch (error) {
133+
return { hasErrors: true }
134+
}
135+
}
136+
137+
/**
138+
* Build assets for production
139+
*/
140+
public async buildForProduction(): Promise<{ hasErrors: boolean }> {
141+
if (!this.buildAssets) {
142+
return { hasErrors: false }
143+
}
144+
145+
if (!this.isEncoreInstalled()) {
146+
return { hasErrors: false }
147+
}
148+
149+
this.notifyAboutEncore()
150+
151+
try {
152+
await this.exec(['production'].concat(this.encoreArgs))
153+
return { hasErrors: false }
154+
} catch (error) {
155+
return { hasErrors: true }
156+
}
157+
}
158+
159+
/**
160+
* Start the webpack dev server
161+
*/
162+
public startDevServer(): { state: 'not-installed' | 'no-assets' | 'running' } {
163+
if (!this.isEncoreInstalled()) {
164+
return { state: 'not-installed' }
165+
}
166+
167+
if (!this.buildAssets) {
168+
return { state: 'no-assets' }
169+
}
170+
171+
const childProcess = execa(
172+
this.binaryName,
173+
['dev-server'].concat(this.encoreArgs),
174+
this.execaOptions
175+
)
176+
177+
childProcess.stdout?.on('data', (line: Buffer) => this.log(line))
178+
childProcess.stderr?.on('data', (line: Buffer) => this.logError(line))
179+
childProcess.on('close', (code, signal) => this.emit('close', { code, signal }))
180+
childProcess.on('exit', (code, signal) => this.emit('exit', { code, signal }))
181+
182+
return { state: 'running' }
183+
}
184+
}

0 commit comments

Comments
 (0)