Skip to content

Commit

Permalink
chore(refactor): Refactor app, improve code and stability
Browse files Browse the repository at this point in the history
An important fix for the way the loaders were being detected and replaced has been addressed.

In this commit we're also introducing:
- Logging
- More types
- New eslint config
  • Loading branch information
harlan-zw committed Jun 22, 2021
1 parent f8e4027 commit 46bae9c
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 147 deletions.
11 changes: 5 additions & 6 deletions src/compatibility.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@

import chalk from 'chalk'
import semver from 'semver'

export function requireNuxtVersion (currentVersion?: string, requiredVersion?: string) {
export function requireNuxtVersion(currentVersion?: string, requiredVersion?: string) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const pkgName = require('../package.json').name

if (!currentVersion || !requireNuxtVersion) {
if (!currentVersion || !requireNuxtVersion)
return
}

const _currentVersion = semver.coerce(currentVersion)!
const _requiredVersion = semver.coerce(requiredVersion)!

if (semver.lt(_currentVersion, _requiredVersion)) {
throw new Error(`\n
${chalk.cyan(pkgName)} is not compatible with your current Nuxt version : ${chalk.yellow('v' + currentVersion)}\n
Required: ${chalk.green('v' + requiredVersion)} or ${chalk.cyan('higher')}
${chalk.cyan(pkgName)} is not compatible with your current Nuxt version : ${chalk.yellow(`v${currentVersion}`)}\n
Required: ${chalk.green(`v${requiredVersion}`)} or ${chalk.cyan('higher')}
`)
}
}
4 changes: 4 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const NAME = 'nuxt-build-optimisations'
export const RISK_PROFILE_SAFE = 'safe'
export const RISK_PROFILE_EXPERIMENTAL = 'experimental'
export const RISK_PROFILE_RISKY = 'risky'
76 changes: 40 additions & 36 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,80 @@
import type { Module } from '@nuxt/types'
import type { ExtendFunctionContext } from '@nuxt/types/config/module'
import type { Configuration as WebpackConfig } from 'webpack'
import defu from 'defu'
import { Configuration as WebpackConfiguration } from 'webpack'
import type { OptimisationArgs, ModuleOptions } from './types'
import { requireNuxtVersion } from './compatibility'
import speedMeasurePlugin from './tools/speed-measure-plugin'
import { webpackOptimiser, imageOptimiser, esbuildOptimiser, nuxtOptimiser } from './optimisations'
import logger from './logger'
import { NAME } from './constants'

const buildOptimisationsModule: Module<ModuleOptions> = function () {
const buildOptimisationsModule: Module<ModuleOptions> = async function(moduleOptions) {
const { nuxt } = this
const defaults = {

requireNuxtVersion(nuxt.constructor.version, '2.10')

const defaultConfig: ModuleOptions = {
measure: false,
measureMode: 'client',
profile: 'experimental',
esbuildMinifyOptions: {
target: 'es2015'
target: 'es2015',
},
esbuildLoaderOptions: {
target: 'es2015'
target: 'es2015',
},
features: {
postcssNoPolyfills: true,
esbuildLoader: true,
esbuildMinifier: true,
imageFileLoader: true,
webpackOptimisations: true,
cacheLoader: true,
hardSourcePlugin: true
}
} as ModuleOptions
const buildOptimisations = {
...defaults,
...nuxt.options.buildOptimisations
} as ModuleOptions

requireNuxtVersion(nuxt.constructor.version, '2.10')
hardSourcePlugin: true,
parallelPlugin: true,
},
}
const options: ModuleOptions = defu.arrayFn(moduleOptions, nuxt.options.buildOptimisations, defaultConfig)

// set measure based on env if the env is set
if (typeof process.env.NUXT_MEASURE !== 'undefined') {
buildOptimisations.measure = process.env.NUXT_MEASURE.toLowerCase() === 'true'
}
if (typeof process.env.NUXT_MEASURE !== 'undefined')
options.measure = process.env.NUXT_MEASURE.toLowerCase() === 'true'

await nuxt.callHook('buildOptimisations:options', options)
logger.debug('post `buildOptimisations:options` hook options', options)

nuxt.hook('build:before', () => {
nuxt.hook('build:before', (nuxt: any) => {
const args = {
options: buildOptimisations,
options,
nuxtOptions: nuxt.options,
env: { isDev: nuxt.dev || process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'dev' }
env: { isDev: nuxt.dev || process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'dev' },
} as OptimisationArgs
// if the user has enabled speed measure plugin and we can

if (process.env.NODE_ENV !== 'test')
logger.info(`\`${NAME}\` enabled with \`${options.profile}\` profile.`)

// boot speed measure plugin
speedMeasurePlugin(args, nuxt)
// if profile is false we don't add any optimisations
if (buildOptimisations.profile === false) {
if (options.profile === false)
return
}
if (process.env.NODE_ENV !== 'test') {
console.info(`\`nuxt-build-optimisations\` enabled with \`${buildOptimisations.profile}\` profile.`)
}
// @ts-ignore

nuxtOptimiser(args)

this.extendBuild((config: WebpackConfig, env: ExtendFunctionContext) => {
args.env = env
this.extendBuild((config: WebpackConfiguration, ctx: ExtendFunctionContext) => {
args.env = ctx
args.config = config
const extendOptimisers = [
webpackOptimiser, imageOptimiser, esbuildOptimiser
]
for (const k in extendOptimisers) {
extendOptimisers[k](args)
}
args.logger = logger.withScope(ctx.isModern ? 'modern' : (ctx.isClient ? 'client' : 'server'))
// call all of them
webpackOptimiser(args)
imageOptimiser(args)
esbuildOptimiser(args)
})
})
}

// @ts-ignore
buildOptimisationsModule.meta = { name: 'nuxt-build-optimisations' }
buildOptimisationsModule.meta = { name: NAME }

export default buildOptimisationsModule
4 changes: 4 additions & 0 deletions src/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import consola from 'consola'
import { NAME } from './constants'

export default consola.withScope(NAME)
98 changes: 47 additions & 51 deletions src/optimisations/esbuild.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,74 @@
import { ESBuildMinifyPlugin } from 'esbuild-loader'
import { RuleSetUseItem } from 'webpack'
import { OptimisationArgs } from '../types'
import { RISK_PROFILE_SAFE } from '../constants'

export default (args : OptimisationArgs) => {
const { options, nuxtOptions, config, env } = args
if (!config.module || !config.plugins) {
export default (args: OptimisationArgs) => {
const { options, nuxtOptions, config, env, logger } = args
if (!config.module || !config.plugins)
return
}

if (options.features.esbuildLoader && (env.isDev || options.profile !== 'safe')) {
// we replace the babel loader with esbuild
if (options.features.esbuildLoader && (env.isDev || options.profile !== RISK_PROFILE_SAFE)) {
const esbuildLoaderOptions = typeof options.esbuildLoaderOptions === 'function'
? options.esbuildLoaderOptions(args)
: options.esbuildLoaderOptions

let cacheLoader = []
// remove the nuxt js/ts loaders
config.module.rules.forEach((rule, ruleKey) => {
config.module.rules.map((rule) => {
// nuxt js / ts file matching
if (!rule.use || !Array.isArray(rule.use) || !rule.test) {
return
}
if (!rule.use || !Array.isArray(rule.use) || !rule.test)
return rule

const test = rule.test as RegExp
const isTypescript = test.test('test.ts')
const isJavascript = test.test('test.js')

if (!isJavascript && !isTypescript)
return rule

// @ts-ignore
cacheLoader = config.module.rules[ruleKey].use.filter((use) => {
return use.loader.includes('cache-loader')
})
if (env.isDev && rule.test.toString() === '/\\.m?jsx?$/i') {
// Need to strip the thread-loader but keep the cache loader
// @ts-ignore
config.module.rules[ruleKey].use = [
...cacheLoader,
{
loader: 'esbuild-loader',
options: {
...esbuildLoaderOptions
}
}
]
} else if (rule.test.toString() === '/\\.ts$/i') {
// @ts-ignore
config.module.rules[ruleKey].use = [
...cacheLoader,
{
loader: 'esbuild-loader',
options: {
loader: 'ts',
...esbuildLoaderOptions
}
}
]
} else if (rule.test.toString() === '/\\.tsx$/i') {
// @ts-ignore
config.module.rules[ruleKey].use = [
...cacheLoader,
{
loader: 'esbuild-loader',
options: {
loader: 'ts',
...esbuildLoaderOptions
}
}
]
const babelLoaderIndex = rule.use.findIndex((use: RuleSetUseItem) => use.loader.includes('babel-loader'))
if (babelLoaderIndex === -1)
return rule

const esbuildLoader = {
loader: 'esbuild-loader',
options: {
...esbuildLoaderOptions,
},
}

// in dev we swap out babel for js
if (env.isDev && isJavascript) {
rule.use.splice(babelLoaderIndex, 1, esbuildLoader)
logger.debug(`JS compilation: swapped out babel-loader at index ${babelLoaderIndex} for esbuild`)
return rule
}

// always swap out typescript builds
esbuildLoader.options.loader = 'ts'
rule.use.splice(babelLoaderIndex, 1, esbuildLoader)
// @ts-ignore
const tsLoaderIndex = rule.use.findIndex((use: RuleSetUseItem) => use.loader.includes('ts-loader'))
rule.use.splice(tsLoaderIndex, 1)
logger.debug(`TS compilation: swapped out ts-loader at index ${tsLoaderIndex} for esbuild`)
return rule
})
}

if (options.features.esbuildMinifier && !env.isDev && options.profile !== 'safe' && nuxtOptions.build.optimization) {
// use esbuild to minify js
if (options.features.esbuildMinifier && !env.isDev && options.profile !== RISK_PROFILE_SAFE && nuxtOptions.build.optimization) {
const esbuildMinifyOptions = typeof options.esbuildMinifyOptions === 'function'
? options.esbuildMinifyOptions(args)
: options.esbuildMinifyOptions
// enable esbuild minifier, replace terser
nuxtOptions.build.optimization.minimize = true
nuxtOptions.build.optimization.minimizer = [
new ESBuildMinifyPlugin(esbuildMinifyOptions)
new ESBuildMinifyPlugin(esbuildMinifyOptions),
]
// make sure terser is off
nuxtOptions.build.terser = false
logger.debug('JS Minify: swapped out terser for esbuild minify')
}
}
25 changes: 13 additions & 12 deletions src/optimisations/images.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
import type { RuleSetRule } from 'webpack'
import type { OptimisationArgs } from '../types'

export default ({ config, env, options } : OptimisationArgs) => {
if (!config.module || !env.isDev || !options.features.imageFileLoader) {
export default ({ config, env, options, logger }: OptimisationArgs) => {
if (!config.module || !env.isDev || !options.features.imageFileLoader)
return
}

const imgLoaders = config.module.rules.filter(r =>
// make sure there is a test available
r.test &&
r.test
// we don't want to match resource queries such as ?inline, it's possible this is nested within oneOf though
!r.resourceQuery &&
&& !r.resourceQuery
// only basic image formats, we don't want to match svg in case there's a specific svg-loader
// @ts-ignore
('.png'.match(r.test) || '.jpg'.match(r.test))
&& ('.png'.match(r.test) || '.jpg'.match(r.test)),
)
// if for some reason there is no png loader
if (!imgLoaders.length) {
if (!imgLoaders.length)
return
}

// only match the first rule we find
const firstImgLoader = imgLoaders[0] as RuleSetRule
// remove the current image loader for pngs
Expand All @@ -35,9 +34,11 @@ export default ({ config, env, options } : OptimisationArgs) => {
loader: 'file-loader',
options: {
name: '[path][name].[ext]',
esModule: false
}
}
]
esModule: false,
},
},
],
})

logger.debug('Image loader: Swapped out url-loader with file loader')
}
Loading

0 comments on commit 46bae9c

Please sign in to comment.