diff --git a/Gruntfile.js b/Gruntfile.js index a26ac7c5..f7f6f182 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -3,7 +3,7 @@ const fs = require('fs'); const path = require('path'); -const webpackConfig = require('./webpack.config'); +const webpackConfig = require('./build/webpack.config'); const pkg = require('./package.json'); module.exports = function(grunt) { diff --git a/build/webpack.config.js b/build/webpack.config.js new file mode 100644 index 00000000..5e1fe99e --- /dev/null +++ b/build/webpack.config.js @@ -0,0 +1,219 @@ +const path = require('path'); + +const webpack = require('webpack'); + +const StringReplacePlugin = require('string-replace-webpack-plugin'); +const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); + +const rootDir = path.join(__dirname, '..'); + +const pkg = require('../package.json'); + +process.noDeprecation = true; // for css loaders + +function config(grunt, mode = 'production') { + const devMode = mode === 'development'; + const date = grunt.config.get('date'); + const dt = date.toISOString().replace(/T.*/, ''); + const year = date.getFullYear(); + return { + mode, + entry: { + app: ['babel-helpers', 'app', 'main.scss'], + vendor: [ + 'jquery', + 'underscore', + 'backbone', + 'morphdom', + 'kdbxweb', + 'baron', + 'pikaday', + 'jsqrcode', + 'argon2-wasm', + 'argon2', + 'marked' + ] + }, + output: { + path: path.resolve('.', 'tmp'), + filename: 'js/[name].js' + }, + target: 'web', + performance: { + hints: false + }, + stats: { + colors: false, + modules: true, + reasons: true + }, + progress: false, + failOnError: true, + resolve: { + modules: [ + path.join(rootDir, 'app/scripts'), + path.join(rootDir, 'app/styles'), + path.join(rootDir, 'node_modules') + ], + alias: { + 'babel-helpers': path.join(rootDir, 'app/lib/babel-helpers.js'), + backbone: `backbone/backbone${devMode ? '-min' : ''}.js`, + underscore: `underscore/underscore${devMode ? '-min' : ''}.js`, + _: `underscore/underscore${devMode ? '-min' : ''}.js`, + jquery: `jquery/dist/jquery${devMode ? '.min' : ''}.js`, + morphdom: `morphdom/dist/morphdom-umd${devMode ? '.min' : ''}.js`, + kdbxweb: 'kdbxweb/dist/kdbxweb.js', + baron: `baron/baron${devMode ? '.min' : ''}.js`, + qrcode: `jsqrcode/dist/qrcode${devMode ? '.min' : ''}.js`, + argon2: 'argon2-browser/dist/argon2.js', + marked: devMode ? 'marked/lib/marked.js' : 'marked/marked.min.js', + dompurify: `dompurify/dist/purify${devMode ? '.min' : ''}.js`, + hbs: 'handlebars/runtime.js', + 'argon2-wasm': 'argon2-browser/dist/argon2.wasm', + templates: path.join(rootDir, 'app/templates'), + 'public-key.pem': path.join(rootDir, 'app/resources/public-key.pem'), + 'demo.kdbx': path.join(rootDir, 'app/resources/Demo.kdbx') + } + }, + module: { + rules: [ + { + test: /\.hbs$/, + loader: StringReplacePlugin.replace('handlebars-loader', { + replacements: [{ pattern: /\r?\n\s*/g, replacement: () => '\n' }] + }) + }, + { + test: /runtime-info\.js$/, + loader: StringReplacePlugin.replace({ + replacements: [ + { + pattern: /@@VERSION/g, + replacement: () => + pkg.version + (grunt.option('beta') ? '-beta' : '') + }, + { + pattern: /@@BETA/g, + replacement: () => (grunt.option('beta') ? '1' : '') + }, + { pattern: /@@DATE/g, replacement: () => dt }, + { + pattern: /@@COMMIT/g, + replacement: () => + grunt.config.get('gitinfo.local.branch.current.shortSHA') + } + ] + }) + }, + { + test: /baron(\.min)?\.js$/, + loader: 'exports-loader?baron; delete window.baron;' + }, + { + test: /babel-helpers\.js$/, + loader: 'exports-loader?global.babelHelpers; delete global.babelHelpers' + }, + { test: /pikaday\.js$/, loader: 'uglify-loader' }, + { test: /handlebars/, loader: 'strip-sourcemap-loader' }, + { + test: /\.js$/, + exclude: /(node_modules|babel-helpers\.js)/, + loader: 'babel-loader', + query: { cacheDirectory: true } + }, + { test: /argon2\.wasm/, type: 'javascript/auto', loader: 'base64-loader' }, + { test: /argon2(\.min)?\.js/, loader: 'raw-loader' }, + { + test: /\.s?css$/, + use: [ + MiniCssExtractPlugin.loader, + { loader: 'css-loader', options: { sourceMap: devMode } }, + { loader: 'sass-loader', options: { sourceMap: devMode } } + ] + }, + { + test: /fonts\/.*\.(woff2|ttf|eot|svg)/, + use: ['url-loader', 'ignore-loader'] + }, + { test: /\.woff$/, loader: 'url-loader' }, + { test: /\.pem$/, loader: 'raw-loader' }, + { test: /\.kdbx$/, loader: 'base64-loader' } + ] + }, + optimization: { + runtimeChunk: 'single', + splitChunks: { + cacheGroups: { + vendor: { + test: /[\\/]node_modules[\\/]/, + name: 'vendor', + chunks: 'all' + } + } + }, + minimizer: [ + new UglifyJsPlugin({ + cache: true, + parallel: true + }), + new OptimizeCSSAssetsPlugin({ + cssProcessorPluginOptions: { + preset: ['default', { discardComments: { removeAll: true } }] + } + }), + new BundleAnalyzerPlugin({ + openAnalyzer: false, + analyzerMode: 'static', + reportFilename: 'stats/analyzer_report.html', + generateStatsFile: true, + statsFilename: 'stats/stats.json' + }) + ] + }, + plugins: [ + new webpack.BannerPlugin( + 'keeweb v' + + pkg.version + + ', (c) ' + + year + + ' ' + + pkg.author.name + + ', opensource.org/licenses/' + + pkg.license + ), + new webpack.ProvidePlugin({ + _: 'underscore', + $: 'jquery', + babelHelpers: 'babel-helpers' + }), + new webpack.IgnorePlugin(/^(moment)$/), + new StringReplacePlugin(), + new MiniCssExtractPlugin({ + filename: 'css/[name].css' + }) + ], + node: { + console: false, + process: false, + crypto: false, + Buffer: false, + __filename: false, + __dirname: false, + fs: false, + setImmediate: false, + path: false + }, + externals: { + xmldom: 'null', + crypto: 'null', + fs: 'null', + path: 'null' + }, + devtool: devMode ? 'source-map' : undefined + }; +} + +module.exports.config = config; diff --git a/webpack.config.js b/webpack.config.js index db5a73d7..bf682a0a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,217 +1,18 @@ +// This file is here for smart IDE's who configure the resolve rules based on webpack.config.js +// This config is never used for building. The real thing is in build/webpack.config.js + const path = require('path'); -const webpack = require('webpack'); - -const StringReplacePlugin = require('string-replace-webpack-plugin'); -const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; -const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); - -const pkg = require('./package.json'); - -process.noDeprecation = true; // for css loaders - -function config(grunt, mode = 'production') { - const devMode = mode === 'development'; - const date = grunt.config.get('date'); - const dt = date.toISOString().replace(/T.*/, ''); - const year = date.getFullYear(); - return { - mode, - entry: { - app: ['babel-helpers', 'app', 'main.scss'], - vendor: [ - 'jquery', - 'underscore', - 'backbone', - 'morphdom', - 'kdbxweb', - 'baron', - 'pikaday', - 'jsqrcode', - 'argon2-wasm', - 'argon2', - 'marked' - ] - }, - output: { - path: path.resolve('.', 'tmp'), - filename: 'js/[name].js' - }, - target: 'web', - performance: { - hints: false - }, - stats: { - colors: false, - modules: true, - reasons: true - }, - progress: false, - failOnError: true, - resolve: { - modules: [ - path.join(__dirname, 'app/scripts'), - path.join(__dirname, 'app/styles'), - path.join(__dirname, 'node_modules') - ], - alias: { - 'babel-helpers': path.join(__dirname, 'app/lib/babel-helpers.js'), - backbone: `backbone/backbone${devMode ? '-min' : ''}.js`, - underscore: `underscore/underscore${devMode ? '-min' : ''}.js`, - _: `underscore/underscore${devMode ? '-min' : ''}.js`, - jquery: `jquery/dist/jquery${devMode ? '.min' : ''}.js`, - morphdom: `morphdom/dist/morphdom-umd${devMode ? '.min' : ''}.js`, - kdbxweb: 'kdbxweb/dist/kdbxweb.js', - baron: `baron/baron${devMode ? '.min' : ''}.js`, - qrcode: `jsqrcode/dist/qrcode${devMode ? '.min' : ''}.js`, - argon2: 'argon2-browser/dist/argon2.js', - marked: devMode ? 'marked/lib/marked.js' : 'marked/marked.min.js', - dompurify: `dompurify/dist/purify${devMode ? '.min' : ''}.js`, - hbs: 'handlebars/runtime.js', - 'argon2-wasm': 'argon2-browser/dist/argon2.wasm', - templates: path.join(__dirname, 'app/templates'), - 'public-key.pem': path.join(__dirname, 'app/resources/public-key.pem'), - 'demo.kdbx': path.join(__dirname, 'app/resources/Demo.kdbx') - } - }, - module: { - rules: [ - { - test: /\.hbs$/, - loader: StringReplacePlugin.replace('handlebars-loader', { - replacements: [{ pattern: /\r?\n\s*/g, replacement: () => '\n' }] - }) - }, - { - test: /runtime-info\.js$/, - loader: StringReplacePlugin.replace({ - replacements: [ - { - pattern: /@@VERSION/g, - replacement: () => - pkg.version + (grunt.option('beta') ? '-beta' : '') - }, - { - pattern: /@@BETA/g, - replacement: () => (grunt.option('beta') ? '1' : '') - }, - { pattern: /@@DATE/g, replacement: () => dt }, - { - pattern: /@@COMMIT/g, - replacement: () => - grunt.config.get('gitinfo.local.branch.current.shortSHA') - } - ] - }) - }, - { - test: /baron(\.min)?\.js$/, - loader: 'exports-loader?baron; delete window.baron;' - }, - { - test: /babel-helpers\.js$/, - loader: 'exports-loader?global.babelHelpers; delete global.babelHelpers' - }, - { test: /pikaday\.js$/, loader: 'uglify-loader' }, - { test: /handlebars/, loader: 'strip-sourcemap-loader' }, - { - test: /\.js$/, - exclude: /(node_modules|babel-helpers\.js)/, - loader: 'babel-loader', - query: { cacheDirectory: true } - }, - { test: /argon2\.wasm/, type: 'javascript/auto', loader: 'base64-loader' }, - { test: /argon2(\.min)?\.js/, loader: 'raw-loader' }, - { - test: /\.s?css$/, - use: [ - MiniCssExtractPlugin.loader, - { loader: 'css-loader', options: { sourceMap: devMode } }, - { loader: 'sass-loader', options: { sourceMap: devMode } } - ] - }, - { - test: /fonts\/.*\.(woff2|ttf|eot|svg)/, - use: ['url-loader', 'ignore-loader'] - }, - { test: /\.woff$/, loader: 'url-loader' }, - { test: /\.pem$/, loader: 'raw-loader' }, - { test: /\.kdbx$/, loader: 'base64-loader' } - ] - }, - optimization: { - runtimeChunk: 'single', - splitChunks: { - cacheGroups: { - vendor: { - test: /[\\/]node_modules[\\/]/, - name: 'vendor', - chunks: 'all' - } - } - }, - minimizer: [ - new UglifyJsPlugin({ - cache: true, - parallel: true - }), - new OptimizeCSSAssetsPlugin({ - cssProcessorPluginOptions: { - preset: ['default', { discardComments: { removeAll: true } }] - } - }), - new BundleAnalyzerPlugin({ - openAnalyzer: false, - analyzerMode: 'static', - reportFilename: 'stats/analyzer_report.html', - generateStatsFile: true, - statsFilename: 'stats/stats.json' - }) - ] - }, - plugins: [ - new webpack.BannerPlugin( - 'keeweb v' + - pkg.version + - ', (c) ' + - year + - ' ' + - pkg.author.name + - ', opensource.org/licenses/' + - pkg.license - ), - new webpack.ProvidePlugin({ - _: 'underscore', - $: 'jquery', - babelHelpers: 'babel-helpers' - }), - new webpack.IgnorePlugin(/^(moment)$/), - new StringReplacePlugin(), - new MiniCssExtractPlugin({ - filename: 'css/[name].css' - }) +module.exports = { + resolve: { + modules: [ + path.join(__dirname, 'app/scripts'), + path.join(__dirname, 'app/styles'), + path.join(__dirname, 'app/resources'), + path.join(__dirname, 'node_modules') ], - node: { - console: false, - process: false, - crypto: false, - Buffer: false, - __filename: false, - __dirname: false, - fs: false, - setImmediate: false, - path: false - }, - externals: { - xmldom: 'null', - crypto: 'null', - fs: 'null', - path: 'null' - }, - devtool: devMode ? 'source-map' : undefined - }; -} - -module.exports.config = config; + alias: { + templates: path.join(__dirname, 'app/templates') + } + } +};