UNPKG

@quasar/app

Version:

Quasar Framework local CLI

381 lines (331 loc) 11.3 kB
const path = require('path') const webpack = require('webpack') const WebpackChain = require('webpack-chain') const VueLoaderPlugin = require('vue-loader/lib/plugin') const WebpackProgress = require('./plugin.progress') const BootDefaultExport = require('./plugin.boot-default-export') const appPaths = require('../app-paths') const injectStyleRules = require('./inject.style-rules') module.exports = function (cfg, configName) { const chain = new WebpackChain() const needsHash = !cfg.ctx.dev && !['electron', 'cordova', 'capacitor'].includes(cfg.ctx.modeName) const fileHash = needsHash ? '.[hash:8]' : '' const chunkHash = needsHash ? '.[contenthash:8]' : '' const resolveModules = [ 'node_modules', appPaths.resolve.app('node_modules'), appPaths.resolve.cli('node_modules') ] if (configName === 'Capacitor') { // need to also look into /src-capacitor // for deps like @capacitor/core resolveModules.push( appPaths.resolve.capacitor('node_modules') ) } chain.entry('app').add(appPaths.resolve.app('.quasar/client-entry.js')) chain.mode(cfg.ctx.dev ? 'development' : 'production') chain.devtool(cfg.build.sourceMap ? cfg.build.devtool : false) if (cfg.ctx.prod || cfg.ctx.mode.ssr) { chain.output .path( cfg.ctx.mode.ssr ? path.join(cfg.build.distDir, 'www') : cfg.build.distDir ) .publicPath(cfg.build.publicPath) .filename(`js/[name]${fileHash}.js`) .chunkFilename(`js/[name]${chunkHash}.js`) } chain.resolve.symlinks(false) chain.resolve.extensions .merge([ '.js', '.vue', '.json' ]) chain.resolve.modules .merge(resolveModules) chain.resolve.alias .merge({ src: appPaths.srcDir, app: appPaths.appDir, components: appPaths.resolve.src(`components`), layouts: appPaths.resolve.src(`layouts`), pages: appPaths.resolve.src(`pages`), assets: appPaths.resolve.src(`assets`), boot: appPaths.resolve.src(`boot`) }) if (cfg.framework.all === true) { chain.resolve.alias.set('quasar$', 'quasar/dist/quasar.esm.js') } if (cfg.build.vueCompiler) { chain.resolve.alias.set('vue$', 'vue/dist/vue.esm.js') } chain.resolveLoader.modules .merge(resolveModules) chain.module.noParse(/^(vue|vue-router|vuex|vuex-router-sync)$/) const vueRule = chain.module.rule('vue') .test(/\.vue$/) if (cfg.framework.all === 'auto') { vueRule.use('quasar-auto-import') .loader(path.join(__dirname, 'loader.auto-import.js')) .options(cfg.framework.autoImportComponentCase) } vueRule.use('vue-loader') .loader('vue-loader') .options({ productionMode: cfg.ctx.prod, compilerOptions: { preserveWhitespace: false }, transformAssetUrls: cfg.build.transformAssetUrls }) chain.module.rule('babel') .test(/\.jsx?$/) .exclude .add(filepath => { // always transpile js(x) in Vue files if (/\.vue\.jsx?$/.test(filepath)) { return false } if (filepath.match(/[\\/]node_modules[\\/]quasar[\\/]/)) { if (configName === 'Server') { // transpile only if not from 'quasar/dist' folder if (!filepath.match(/[\\/]node_modules[\\/]quasar[\\/]dist/)) { return false } } else { // always transpile Quasar return false } } if (cfg.build.transpileDependencies.some(dep => filepath.match(dep))) { return false } // Don't transpile anything else in node_modules return /[\\/]node_modules[\\/]/.test(filepath) }) .end() .use('babel-loader') .loader('babel-loader') .options({ extends: appPaths.resolve.app('babel.config.js'), plugins: cfg.framework.all !== true && configName !== 'Server' ? [ [ 'transform-imports', { quasar: { transform: `quasar/dist/babel-transforms/imports.js`, preventFullImport: true } } ] ] : [] }) chain.module.rule('images') .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/) .use('url-loader') .loader('url-loader') .options({ esModule: false, limit: 10000, name: `img/[name]${fileHash}.[ext]` }) chain.module.rule('fonts') .test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/) .use('url-loader') .loader('url-loader') .options({ esModule: false, limit: 10000, name: `fonts/[name]${fileHash}.[ext]` }) chain.module.rule('media') .test(/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/) .use('url-loader') .loader('url-loader') .options({ esModule: false, limit: 10000, name: `media/[name]${fileHash}.[ext]` }) injectStyleRules(chain, { rtl: cfg.build.rtl, sourceMap: cfg.build.sourceMap, extract: cfg.build.extractCSS, serverExtract: configName === 'Server' && cfg.build.extractCSS, minify: cfg.build.minify, stylusLoaderOptions: cfg.build.stylusLoaderOptions, sassLoaderOptions: cfg.build.sassLoaderOptions, scssLoaderOptions: cfg.build.scssLoaderOptions, lessLoaderOptions: cfg.build.lessLoaderOptions }) chain.plugin('vue-loader') .use(VueLoaderPlugin) chain.plugin('define') .use(webpack.DefinePlugin, [ cfg.build.env ]) if (cfg.build.showProgress) { chain.plugin('progress') .use(WebpackProgress, [{ name: configName }]) } chain.plugin('boot-default-export') .use(BootDefaultExport) chain.performance .hints(false) .maxAssetSize(500000) if (configName !== 'Server' && cfg.vendor.disable !== true) { const { add, remove } = cfg.vendor const regex = /[\\/]node_modules[\\/]/ chain.optimization .splitChunks({ cacheGroups: { vendors: { name: 'vendor', chunks: 'all', priority: -10, // a module is extracted into the vendor chunk if... test: add !== void 0 || remove !== void 0 ? module => { if (module.resource) { if (add !== void 0 && add.test(module.resource)) { return true } if (remove !== void 0 && remove.test(module.resource)) { return false } } return regex.test(module.resource) } : module => regex.test(module.resource) }, common: { name: `chunk-common`, minChunks: 2, priority: -20, chunks: 'all', reuseExistingChunk: true } } }) // extract webpack runtime and module manifest to its own file in order to // prevent vendor hash from being updated whenever app bundle is updated if (cfg.build.webpackManifest) { chain.optimization.runtimeChunk('single') } } // DEVELOPMENT build if (cfg.ctx.dev) { const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') const { devCompilationSuccess } = require('../helpers/banner') chain.optimization .noEmitOnErrors(true) chain.plugin('friendly-errors') .use(FriendlyErrorsPlugin, [{ clearConsole: true, compilationSuccessInfo: ['spa', 'pwa', 'ssr'].includes(cfg.ctx.modeName) ? { notes: [ devCompilationSuccess(cfg.ctx, cfg.build.APP_URL, appPaths.appDir) ] } : undefined }]) } // PRODUCTION build else { // keep module.id stable when vendor modules does not change chain.plugin('hashed-module-ids') .use(webpack.HashedModuleIdsPlugin, [{ hashDigest: 'hex' }]) // keep chunk ids stable so async chunks have consistent hash const hash = require('hash-sum') chain .plugin('named-chunks') .use(webpack.NamedChunksPlugin, [ chunk => chunk.name || hash( Array.from(chunk.modulesIterable, m => m.id).join('_') ) ]) if (configName !== 'Server') { // copy statics to dist folder const CopyWebpackPlugin = require('copy-webpack-plugin') chain.plugin('copy-webpack') .use(CopyWebpackPlugin, [ [{ from: appPaths.resolve.src('statics'), to: 'statics', ignore: ['.*'], ignore: ['.*'].concat( // avoid useless files to be copied ['electron', 'cordova', 'capacitor'].includes(cfg.ctx.modeName) ? [ 'icons/*', 'app-logo-128x128.png' ] : [] ) }] ]) } // Scope hoisting ala Rollupjs if (cfg.build.scopeHoisting) { chain.optimization .concatenateModules(true) } if (cfg.ctx.debug) { // reset default webpack 4 minimizer chain.optimization.minimizers.delete('js') // also: chain.optimization.minimize(false) } else if (cfg.build.minify) { const TerserPlugin = require('terser-webpack-plugin') chain.optimization .minimizer('js') .use(TerserPlugin, [{ terserOptions: cfg.build.uglifyOptions, extractComments: false, cache: true, parallel: true, sourceMap: cfg.build.sourceMap }]) } // configure CSS extraction & optimize if (configName !== 'Server' && cfg.build.extractCSS) { const MiniCssExtractPlugin = require('mini-css-extract-plugin') // extract css into its own file chain.plugin('mini-css-extract') .use(MiniCssExtractPlugin, [{ filename: 'css/[name].[contenthash:8].css' }]) // dedupe & minify CSS (only if extracted) if (cfg.build.minify) { const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') const cssProcessorOptions = { parser: require('postcss-safe-parser'), autoprefixer: { disable: true } } if (cfg.build.sourceMap) { cssProcessorOptions.map = { inline: false } } // We are using this plugin so that possible // duplicated CSS = require(different components) can be deduped. chain.plugin('optimize-css') .use(OptimizeCSSPlugin, [{ canPrint: false, cssProcessor: require('cssnano'), cssProcessorOptions, cssProcessorPluginOptions: { preset: ['default', { mergeLonghand: false, convertValues: false, cssDeclarationSorter: false, reduceTransforms: false }] } }]) } } if (configName !== 'Server') { // also produce a gzipped version if (cfg.build.gzip) { const CompressionWebpackPlugin = require('compression-webpack-plugin') chain.plugin('compress-webpack') .use(CompressionWebpackPlugin, [ cfg.build.gzip ]) } if (cfg.build.analyze) { const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin chain.plugin('bundle-analyzer') .use(BundleAnalyzerPlugin, [ Object.assign({}, cfg.build.analyze) ]) } } } return chain }