JHipster一知半解- 4.4 打包工具-webpack

回文集目录:JHipster一知半解

概述

在JHipster4之前,Jhipster用的是gulp进行打包和自动化构建,不过,前端技术近几年更新的太快了,马上有出现了模块化的解决方案webpack,成为了前端打包框架的引领者,Jhipster很快就跟上了潮流,在JHipster4,就改为使用webpack进行打包.

webpack的基本理念

首先,Gulp/Grunt是一种能够优化前端的开发流程的工具,而WebPack是一种模块化的解决方案,因而能替代Gulp。

gulp的思路比较类似后端的java工程的流程,按照输入的内容进行编译组合压缩,最终生成所需要的目标文件。
Webpack的工作方式是:把你的单页项目当做一个整体,通过一个给定的主文件(一般来说是index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。
可以看出,webpack的理念很新颖,重复利用了现代前端工程是一个SPA,从这个目标作为生成起点,组织和控制模块依赖和编译的过程,从而实现对模块划分和模块热替换等核心功能。

说明:以下主要是由webpack文档整理的笔记,如果已经阅读过官网,可直接跳过

webpack核心概念

  1. 入口起点(Entry Points)
  2. 输出(Output)
  3. 加载器(Loaders)
  4. 插件(Plugins)
    插件是webpack的支柱功能,相对其他3个,插件相对复杂,但是最强大,加载器做不到的任何构建工作,都由插件解决。作为webpack的使用者,对于其内部原理可以不做深究,但是具体但是使用方式和常用的插件,还是有必要了解的。 下面说明一下基本用法,常用插件将在后面列出。
    webpack插件基本用法:在webpack.config.js中配置
    a.在头部使用require引入插件
    b.在plugins属性中传入插件的new实例(注意new时候的参数)

webpack其他概念

  1. 配置(Configuration)
    webpack 配置是标准的 Node.js CommonJS 模块,你可以做到以下事情:
  • 通过 require(...) 导入其他文件
  • 通过 require(...) 使用 npm 的工具函数
  • 使用 JavaScript 控制流表达式,例如 ?: 操作符
  • 对常用值使用常量或变量
  • 编写并执行函数来生成部分配置
  1. 模块(Modules)
    webpack 通过 loader 可以支持各种语言和预处理器编写模块。loader 描述了 webpack 如何处理 非 JavaScript(non-JavaScript) 模块,并且在bundle中引入这些依赖

  2. 模块解析(Module Resolution)
    支持import或者require两种格式(默认js配置文件用的是require模式,但是typescript用的是import格式)
    三种路径格式:绝对路径,相对路径,模块路径
    解析loader:可配置独立的解析规则

  3. 依赖图表(Dependency Graph)

  4. 文件清单(Manifest)
    当编译器(compiler)开始执行、解析和映射应用程序时,它会保留所有模块的详细要点。这个数据集合称为 "Manifest",当完成打包并发送到浏览器时,会在运行时通过 Manifest 来解析和加载模块,这个文件就相当于源代码的字典,方便编译器快速查找文件信息。

  5. 构建目标(Targets)
    一般来说,可以在webpak.config.js中看见

module.exports = {
  target: 'node'
};

意思就是:webpack 会编译为用于「类 Node.js」环境(使用 Node.js 的 require ,而不是使用任意内置模块(如 fs 或 path)来加载 chunk)。
默认值是web也不是node。

  1. 模块热替换(Hot Module Replaceme)
    HMR提供了实时编译,实时加载,显著提高开发速度。(只更新变更的部分,而不是全工程重新编译,速度快)编译器中,发出update,更新文件清单(JSON)和变更的chunk(JS),内部通过解析更新chunk所影响的模块树(Complete module tr)进行更新。
    webpack-dev-server支持模块热替换,是开发时候的首选服务器。

webpack 配置文档

webpack 是需要传入一个配置对象(configuration object),webpack.config.js文件需要export一个配置对象。看懂、会写、会改的基础就是了解这个配置对象到底能配置哪些东西,它们的功能是什么?
具体看文档

Jhipster webpack目录(基于4.12.0)

Utils.js

引入了fs,path,xml2js三个node内置的工具lib

  • 导出parseVersion(使用xml2js读取pom.xml中project.version属性),
  • 导出_root为当前目录上一级(也就是工程目录),
  • 导出isExternalLib工具函数,判断该外部库是否存在于“/node_modules/”目录中

webpack.common.js

符合官方指引里面的拆分模式,把公用部分提取到common.js中

// const部分,引入后面需要的变量
const webpack = require('webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const StringReplacePlugin = require('string-replace-webpack-plugin');
const MergeJsonWebpackPlugin = require("merge-jsons-webpack-plugin");
//这里引用了工具utils.js
const utils = require('./utils.js');

module.exports = (options) => {
    //构造DATAS,用来传递给StringReplacePlugin
    const DATAS = {
        VERSION: `'${utils.parseVersion()}'`,
        DEBUG_INFO_ENABLED: options.env === 'development',
        // The root URL for API calls, ending with a '/' - for example: `"http://www.jhipster.tech:8081/myservice/"`.
        // If this URL is left empty (""), then it will be relative to the current context.
        // If you use an API server, in `prod` mode, you will need to enable CORS
        // (see the `jhipster.cors` common JHipster property in the `application-*.yml` configurations)
        //注意这里配置了API服务器基础地址,默认为当前上下文,我们后端是java的,不会是这个地址的。
        SERVER_API_URL: `""`
    };
    return {
        resolve: {
            extensions: ['.ts', '.js'],
            modules: ['node_modules']
        },
        stats: {
            children: false
        },
        module: {
            rules: [
                //使用了imports-loader打包bootstrap插件,不过bootstap4.0.0后,好像不在umd目录里面?(存疑)
                { test: /bootstrap\/dist\/js\/umd\//, loader: 'imports-loader?jQuery=jquery' },
                //使用html-loader加载除了首页之外的html页面
                //loader的参数传递是用url的get格式,?后面带参数,&分隔
                {
                    test: /\.html$/,
                    loader: 'html-loader',
                    options: {
                        minimize: true,
                        caseSensitive: true,
                        removeAttributeQuotes:false,
                        minifyJS:false,
                        minifyCSS:false
                    },
                    exclude: ['./src/main/webapp/index.html']
                },
                //使用file-loader读取图片,字体文件
                //?后面/i的含义?
                {
                    test: /\.(jpe?g|png|gif|svg|woff2?|ttf|eot)$/i,
                    loaders: ['file-loader?hash=sha512&digest=hex&name=content/[hash].[ext]']
                },
                //file-loader加载manifest.webapp文件,全目录就一个就一个
                {
                    test: /manifest.webapp$/,
                    loader: 'file-loader?name=manifest.webapp!web-app-manifest-loader'
                },
                //这里就是使用DATRAS的地方,通过传入变量,替换掉app.constants.ts里面的变量值,达到动态改变版本号,开发模式,服务器地址等信息。
                {
                    test: /app.constants.ts$/,
                    loader: StringReplacePlugin.replace({
                        replacements: [{
                            pattern: /\/\* @toreplace (\w*?) \*\//ig,
                            replacement: (match, p1, offset, string) => `_${p1} = ${DATAS[p1]};`
                        }]
                    })
                }
            ]
        },
        plugins: [
            //设置参数中env给node环境变量
            new webpack.DefinePlugin({
                'process.env': {
                    'NODE_ENV': JSON.stringify(options.env)
                }
            }),
            //提取公用的Chunk的文件,
            new webpack.optimize.CommonsChunkPlugin({
                name: 'polyfills',
                chunks: ['polyfills']
            }),
            //这里使用utils工具,如果是在node_modules目录的,就打包成vendor。
            new webpack.optimize.CommonsChunkPlugin({
                name: 'vendor',
                chunks: ['main'],
                minChunks: module => utils.isExternalLib(module)
            }),
            new webpack.optimize.CommonsChunkPlugin({
                name: ['polyfills', 'vendor'].reverse()
            }),
            new webpack.optimize.CommonsChunkPlugin({
                name: ['manifest'],
                minChunks: Infinity,
            }),
            /**
             * See: https://github.com/angular/angular/issues/11580
             */
            new webpack.ContextReplacementPlugin(
                /(.+)?angular(\\|\/)core(.+)?/,
                utils.root('src/main/webapp/app'), {}
            ),
            //这里把直接使用的进行拷贝,发现swagger-ui其实分了2部分,一部分是包含在node_modules的公用部分,一部分是Jhipster定制化的,包含在/src/main/webapp里面
            new CopyWebpackPlugin([
                { from: './node_modules/swagger-ui/dist/css', to: 'swagger-ui/dist/css' },
                { from: './node_modules/swagger-ui/dist/lib', to: 'swagger-ui/dist/lib' },
                { from: './node_modules/swagger-ui/dist/swagger-ui.min.js', to: 'swagger-ui/dist/swagger-ui.min.js' },
                { from: './src/main/webapp/swagger-ui/', to: 'swagger-ui' },
                { from: './src/main/webapp/favicon.ico', to: 'favicon.ico' },
                { from: './src/main/webapp/manifest.webapp', to: 'manifest.webapp' },
                // jhipster-needle-add-assets-to-webpack - JHipster will add/remove third-party resources in this array
                { from: './src/main/webapp/robots.txt', to: 'robots.txt' }
            ]),
            //jQuery适配,定义$为全局变量,这样用起来方便多了。
            new webpack.ProvidePlugin({
                $: "jquery",
                jQuery: "jquery"
            }),
            //由于Angular是统一读取i18n文件的,而全部写在一个文件里面显然不适合开发维护,MergeJsonWebpackPlugin很好地解决了这个问题,把一个目录里面所有的翻译文本都整合成同一个json文件。
            new MergeJsonWebpackPlugin({
                output: {
                    groupBy: [
                        { pattern: "./src/main/webapp/i18n/en/*.json", fileName: "./i18n/en.json" },
                        { pattern: "./src/main/webapp/i18n/fr/*.json", fileName: "./i18n/fr.json" }
                        // jhipster-needle-i18n-language-webpack - JHipster will add/remove languages in this array
                    ]
                }
            }),
            //使用HtmlWebpackPlugin,修改index.html,把打包好的js放进去
            new HtmlWebpackPlugin({
                template: './src/main/webapp/index.html',
                chunksSortMode: 'dependency',
                inject: 'body'
            }),
            new StringReplacePlugin()
        ]
    };
};

webpack.dev.js

const webpack = require('webpack');
const writeFilePlugin = require('write-file-webpack-plugin');
const webpackMerge = require('webpack-merge');
const BrowserSyncPlugin = require('browser-sync-webpack-plugin');
const WebpackNotifierPlugin = require('webpack-notifier');
const path = require('path');
//这里引入了utils和common配置
const utils = require('./utils.js');
const commonConfig = require('./webpack.common.js');

//定义开发模式
const ENV = 'development';
//使用webpackMerge把common配置当前文件合并
module.exports = webpackMerge(commonConfig({ env: ENV }), {
    //sourcemap模式为eval而不是inline,方便IDE进行断点跟踪,经过测试,Webstorm和vscode都可以成功断点到ts中,很强大。
    devtool: 'eval-source-map',
    //配置开发服务器,proxy模块很强大,访问这些地址,会自动转发,适用于前后端分离的开发测试。
    devServer: {
        contentBase: './target/www',
        proxy: [{
            context: [
                /* jhipster-needle-add-entity-to-webpack - JHipster will add entity api paths here */
                '/api',
                '/management',
                '/swagger-resources',
                '/v2/api-docs',
                '/h2-console',
                '/auth'
            ],
            target: 'http://127.0.0.1:8080',
            secure: false
        }],
        //node_modules东西太多,就不要监控变化了。
        watchOptions: {
            ignored: /node_modules/
        }
    },
    //多入口,main为主程序,polyfills为兼容性因子,?那css为什么要单独列出?
    entry: {
        polyfills: './src/main/webapp/app/polyfills',
        global: './src/main/webapp/content/css/global.css',
        main: './src/main/webapp/app/app.main'
    },
    //输出:实际在target/www/dist中输出
    output: {
        path: utils.root('target/www'),
        filename: 'app/[name].bundle.js',
        chunkFilename: 'app/[id].chunk.js'
    },
    module: {
        rules: [
            //tslint-loader加载校验.ts文件,这个是比下面两个要早进行的,所以用pre。
            {
                test: /\.ts$/,
                enforce: 'pre',
                loaders: 'tslint-loader',
                exclude: ['node_modules', new RegExp('reflect-metadata\\' + path.sep + 'Reflect\\.ts')]
            },
            //用angular2特有的angular2-template-loader再次对ts编译
            {
                test: /\.ts$/,
                loaders: [
                    'angular2-template-loader',
                    'awesome-typescript-loader'
                ],
                exclude: ['node_modules/generator-jhipster']
            },
            //把不在vendor和golobal目录的css都用to-string-loade,css-loader加载
            {
                test: /\.css$/,
                loaders: ['to-string-loader', 'css-loader'],
                exclude: /(vendor\.css|global\.css)/
            },
            //在vendor和golobal目录的css用style-loader,css-loader加载
            {
                test: /(vendor\.css|global\.css)/,
                loaders: ['style-loader', 'css-loader']
            }
        ]
    },
    plugins: [
        //开发模式特有的同步插件,HMR热替换使用
        new BrowserSyncPlugin({
            host: 'localhost',
            port: 9000,
            proxy: {
                target: 'http://localhost:9060'
            }
        }, {
            reload: false
        }),
        new webpack.NoEmitOnErrorsPlugin(),
        new webpack.NamedModulesPlugin(),
        new writeFilePlugin(),
        new webpack.WatchIgnorePlugin([
            utils.root('src/test'),
        ]),
        new WebpackNotifierPlugin({
            title: 'JHipster',
            contentImage: path.join(__dirname, 'logo-jhipster.png')
        })
    ]
});

webpack.prod.js

const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const Visualizer = require('webpack-visualizer-plugin');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const WorkboxPlugin = require('workbox-webpack-plugin');
//这是4.12加的,使用了新的@ngtools/webpack进行编译,只前用的ngc-webpack已过时。
const AngularCompilerPlugin = require('@ngtools/webpack').AngularCompilerPlugin;
const path = require('path');

const utils = require('./utils.js');
const commonConfig = require('./webpack.common.js');
//定义生产环节变量
const ENV = 'production';
const extractCSS = new ExtractTextPlugin(`[name].[hash].css`);

module.exports = webpackMerge(commonConfig({ env: ENV }), {
    // 虽然webpack官方推荐打开sourc-map,不过毕竟构建过程会变很慢,默认就关闭了 
    // Enable source maps. Please note that this will slow down the build.
    // You have to enable it in UglifyJSPlugin config below and in tsconfig-aot.json as well
    // devtool: 'source-map',
    //多入口,与dev一样
    entry: {
        polyfills: './src/main/webapp/app/polyfills',
        global: './src/main/webapp/content/css/global.css',
        main: './src/main/webapp/app/app.main'
    },
    //输出,与dev也一样
    output: {
        path: utils.root('target/www'),
        filename: 'app/[name].[hash].bundle.js',
        chunkFilename: 'app/[id].[hash].chunk.js'
    },
    module: {
        rules: [
            //使用@ngtools/webpack编译angular的js和ts文件
            {
                test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/,
                use: ['@ngtools/webpack']
            },
            //和dev一样
            {
                test: /\.css$/,
                loaders: ['to-string-loader', 'css-loader'],
                exclude: /(vendor\.css|global\.css)/
            },
            //和dev不同,增加了分离css的extractCSS。
            {
                test: /(vendor\.css|global\.css)/,
                use: extractCSS.extract({
                    fallback: 'style-loader',
                    use: ['css-loader']
                })
            }
        ]
    },
    plugins: [
        //参考(ExtractTextPlugin插件) https://www.cnblogs.com/sloong/p/5826818.html
        extractCSS,
        //在target,生成stats.html,是个打包结果分析的饼图,可以查看每个模块大小,占比。
        new Visualizer({
            // Webpack statistics in target folder
            filename: '../stats.html'
        }),
        //压缩插件
        new UglifyJSPlugin({
            parallel: true,
            uglifyOptions: {
                ie8: false,
                // sourceMap: true, // Enable source maps. Please note that this will slow down the build
                compress: {
                    dead_code: true,
                    warnings: false,
                    properties: true,
                    drop_debugger: true,
                    conditionals: true,
                    booleans: true,
                    loops: true,
                    unused: true,
                    toplevel: true,
                    if_return: true,
                    inline: true,
                    join_vars: true
                },
                output: {
                    comments: false,
                    beautify: false,
                    indent_level: 2
                }
            }
        }),
        //定义angular编译器插件的参数
        new AngularCompilerPlugin({
            mainPath: utils.root('src/main/webapp/app/app.main.ts'),
            tsConfigPath: utils.root('tsconfig-aot.json'),
            sourceMap: true
        }),
        new webpack.LoaderOptionsPlugin({
            minimize: true,
            debug: false
        }),
        //定义workbox插件,谷歌技术,暂时没研究
        //参考https://segmentfault.com/a/1190000012326428
        new WorkboxPlugin({
            // to cache all under target/www
            globDirectory: utils.root('target/www'),
            // find these files and cache them
            globPatterns: ['**/*.{html,bundle.js,css,png,svg,jpg,gif,json}'],
            // create service worker at the target/www
            swDest: path.resolve(utils.root('target/www'), 'sw.js'),
            clientsClaim: true,
            skipWaiting: true,
        })
    ]
});

webpack.test.js和webpack.vendor.js

  • webpack.test.js是给karma测试时候用的,在测试模块中再详细看(它不依赖于webpack.common.js)
  • webpack.vendor.js,找了一圈也没发现使用的地方,独立调用webpack对其打包,还有错误(ng2-webstorage已被ngx-webstorage替代,ng-jhipster也没法打包进去),貌似是遗留的配置,当前并没有使用到。

package.json中webpack-cli命令调用

{
"scripts": {
    "webpack:dev": "yarn run webpack-dev-server -- --config webpack/webpack.dev.js --progress --inline --hot --profile --port=9060 --watch-content-base",
    "webpack:build:main": "yarn run webpack -- --config webpack/webpack.dev.js --progress --profile",
    "webpack:build": "yarn run cleanup && yarn run webpack:build:main",
    "webpack:prod:main": "yarn run webpack -- --config webpack/webpack.prod.js --progress --profile",
    "webpack:prod": "yarn run cleanup && yarn run webpack:prod:main && yarn run clean-www",
    "webpack:test": "yarn run test",
    "webpack-dev-server": "node --max_old_space_size=4096 node_modules/webpack-dev-server/bin/webpack-dev-server.js",
    "webpack": "node --max_old_space_size=4096 node_modules/webpack/bin/webpack.js",
    }
}

angular的ng build

从上面可以看出,webpack还是有一定的学习门槛的,使用起来也颇为不易,有一些工程,比如PrimeNG,就只是使用了Angular自带的ng build构建,ng serve运行,而不是使用原生的webpack配置。主要原因有两个

  1. 由于Jhipster是在angular-cli还在开发过程中就开始的工程,所以工程用的还是原生的webpack配置,而不是集成在cli的ng build命令。
  2. 使用Angular自带的cli命令固然方便,工程目录也好看,但是底层本质上还是用webpack(@ngtools/webpack插件),带来方便的同时也丧失了一定的灵活性。比如,他对文件的位置m目录有要求(要由ng init 构建出来的工程),还有它通过读取.angular-cli.json文件传递给webpack进行配置启动的,如果我们的项目需要引入第三方外部库,比如Jquery,就必须手工编辑.angular-cli.json文件。
    3.ng贴心的提供了eject命令,如果不想用默认配置,受制于@ngtools/webpack,自己配置webpack,那就eject出来。
PrimeNG里面.angular-cli.json的相关部分
{
      "styles": [
        "../node_modules/fullcalendar/dist/fullcalendar.min.css",
        "../node_modules/quill/dist/quill.snow.css",
        "../node_modules/font-awesome/css/font-awesome.min.css",
        "styles.css"
      ],
      "scripts": [
        "../node_modules/jquery/dist/jquery.js",
        "../node_modules/moment/moment.js",
        "../node_modules/chart.js/dist/Chart.js",
        "../node_modules/fullcalendar/dist/fullcalendar.js",
        "../node_modules/quill/dist/quill.js",
        "../node_modules/prismjs/prism.js",
        "../node_modules/prismjs/components/prism-typescript.js"
      ],
}
参考资料
  • Webpack官网
    https://webpack.js.org/
  • Webpack中文文档
    https://doc.webpack-china.org/
  • 入门Webpack,看这篇就够了
    https://www.jianshu.com/p/42e11515c10f

你可能感兴趣的:(JHipster一知半解- 4.4 打包工具-webpack)