回文集目录: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核心概念
- 入口起点(Entry Points)
- 输出(Output)
- 加载器(Loaders)
- 插件(Plugins)
插件是webpack的支柱功能,相对其他3个,插件相对复杂,但是最强大,加载器做不到的任何构建工作,都由插件解决。作为webpack的使用者,对于其内部原理可以不做深究,但是具体但是使用方式和常用的插件,还是有必要了解的。 下面说明一下基本用法,常用插件将在后面列出。
webpack插件基本用法:在webpack.config.js中配置
a.在头部使用require引入插件
b.在plugins属性中传入插件的new实例(注意new时候的参数)
webpack其他概念
- 配置(Configuration)
webpack 配置是标准的 Node.js CommonJS 模块,你可以做到以下事情:
- 通过 require(...) 导入其他文件
- 通过 require(...) 使用 npm 的工具函数
- 使用 JavaScript 控制流表达式,例如 ?: 操作符
- 对常用值使用常量或变量
- 编写并执行函数来生成部分配置
模块(Modules)
webpack 通过 loader 可以支持各种语言和预处理器编写模块。loader 描述了 webpack 如何处理 非 JavaScript(non-JavaScript) 模块,并且在bundle中引入这些依赖模块解析(Module Resolution)
支持import或者require两种格式(默认js配置文件用的是require模式,但是typescript用的是import格式)
三种路径格式:绝对路径,相对路径,模块路径
解析loader:可配置独立的解析规则依赖图表(Dependency Graph)
文件清单(Manifest)
当编译器(compiler)开始执行、解析和映射应用程序时,它会保留所有模块的详细要点。这个数据集合称为 "Manifest",当完成打包并发送到浏览器时,会在运行时通过 Manifest 来解析和加载模块,这个文件就相当于源代码的字典,方便编译器快速查找文件信息。构建目标(Targets)
一般来说,可以在webpak.config.js中看见
module.exports = {
target: 'node'
};
意思就是:webpack 会编译为用于「类 Node.js」环境(使用 Node.js 的 require ,而不是使用任意内置模块(如 fs 或 path)来加载 chunk)。
默认值是web也不是node。
- 模块热替换(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配置。主要原因有两个
- 由于Jhipster是在angular-cli还在开发过程中就开始的工程,所以工程用的还是原生的webpack配置,而不是集成在cli的ng build命令。
- 使用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