Webpack打包arcgis js api 3.x纯html+JS+CSS项目
需求
小项目。纯HTML+JS+CSS已经部署上线,但是没有做混淆加密,需要进行混淆加密
分析
目前代码里面需要混淆加密的有main.js,其他的不用混淆加密。所以只需要对main.js进行混淆加密就可,但是要保证混淆加密之后能够访问方法。由于目前在index.html的script使用import导入main.js里面的方法,需要有名字,但是打包之后一般会报错找不到这个名字的模块,因为不是从html里面的script进行打包的,所以在script里面引入打包后的main.js(bundle.js)是不会引入成功的因为模块方法变了。因为做了变量名混淆。所以把html script方法放到main.js里面。
实践
入口文件在libs/mian.js 最终webpack打包代码,其中有两个关键点:
1. path: path.resolve(__dirname, ‘a-dist’),// 自定义明明,因为要推到服务器,所以不使用dist命名;
2. TerserPlugin中的混淆取消,只用去除console和代码注释,不能将混淆打开,否则会报错,和另一个混淆工具冲突;
// webpack.config.js
const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
// 加密
const WebpackObfuscator = require('webpack-obfuscator');
module.exports = {
entry: './libs/main.js',
mode: 'production',
output: {
filename: 'bundle.js',
// libraryTarget: 'amd', // 不用这样设置。因为改了引入esri js api模块的方式,不用AMD方式,改用esri-loader的自定义loadModules。原理是动态加载,避开由于使用require找不到对应模块的问题
path: path.resolve(__dirname, 'a-dist'),// 自定义明明,因为要推到服务器,所以不使用dist
},
devtool: 'source-map',
devServer: {
// contentBase: path.join(__dirname, ''),
compress: true,
port: 8080,
open: true, historyApiFallback: {
index: 'index.html',
},
},
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
// mangle: true, // 是否混淆变量名,默认为 true。因为引入了WebpackObfuscator,先注释
compress: {
// 压缩选项
drop_console: true, // 是否去除控制台输出,默认为 false
drop_debugger: true, // 是否去除调试语句,默认为 false
},
output: {
beautify: false, // 是否美化输出,默认为 false
comments: false, // 是否保留注释,默认为 true
},
// 更多选项请参考 Terser 文档
},
}),
],
},
plugins: [
new ProgressBarPlugin(),
new HtmlWebpackPlugin({
template: 'index.html', // 指定HTML模板文件的路径
filename: 'index.html' // 生成的HTML文件名,默认为index.html
// 还可以添加其他配置选项,如title、favicon等
}),
new CopyWebpackPlugin({
patterns: [
{ from: 'img', to: 'img' },
{ from: 'static', to: 'static' },
{
from: 'libs', to: 'libs', filter: (resourcePath) => {
// 在这里添加你想要忽略的文件或目录的逻辑判断。因为我的项目结构原因,打包时候不能把main.js也复制过去,排除掉。
return !resourcePath.endsWith('main.js') && !resourcePath.endsWith('map-action.js');
}/* globOptions: {
ignore: ['libs/main.js', 'libs/map-action.js'] // 设置要忽略的文件或目录的匹配模式
} */
}, // 指定要拷贝的文件或目录,从来源路径到目标路径
// 可以继续添加其他的规则
],
}),
// 目前没有和TerserPlugin方法混淆,因为terserOptions中的mangle没有设置以为true
new WebpackObfuscator({
rotateUnicodeArray: true,// 启用 Unicode 数组的字符旋转。默认值为 true
compact: true,// 合并和混淆输出代码。默认值为 true。
selfDefending: true,// 生成自保护的混淆代码。默认值为 true。
stringArray: true,// 启用字符串数组混淆。默认值为 true。
stringArrayEncoding: ['base64', 'rc4'],// 指定字符串数组的编码方式。可选值为 'base64'、'rc4' 和 'none'。默认值为 'base64'。
}),
],
// externals: {
// esri: 'esri', // 注意不用在这里在引入一次
// },
module: {
rules: [
// JS 文件的加载器
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader',
},
// CSS 文件的加载器
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
};
打包结束
难点
Webpack打包原理中会解析模块引入语句,如import、require找到对应模块,但是esri在html+JS+CSS获取是通过require获取动态链接。问题来了,那为什么一开始arcgis js用require来请求?为什么不用import?但是一般情况下引入require是没有这个方法的,那require从哪里来的?参考:ArcGIS api for js中的require()究竟是什么。得出require()方法从init.js里面来。
为了配合webpack打包,不能使用require()进行加载esri模块,换种方式请求!查了一下集成和请求模块方式,发现有esri-loader,其中有个方法loadModules。如下代码
esri-loader原理
esri-loader具体原理如下(ChatGPT 3.5的回答):
动态加载:esri-loader 使用动态脚本加载技术来加载 ArcGIS API for
JavaScript。它会根据用户的需求,在运行时动态地向 HTML 页面中插入<script>标签,从指定的 CDN或本地目录加载相关的 JavaScript 文件。异步加载:ArcGIS API for JavaScript 包含多个 JavaScript文件,有些文件可能比较大。为了提高页面加载速度,esri-loader 使用异步加载方式,按需加载所需的文件。这样可以避免一次性加载整个 API,而只加载需要的部分。
回调处理:当相关 JavaScript 文件加载完成后,esri-loader 会触发用户指定的回调函数。在回调函数中,您可以安全地使用ArcGIS API for JavaScript 的模块和类。
模块导入:esri-loader 通过自定义的 loadModules方法,简化了模块的导入过程。它允许您以数组形式传入需要导入的模块名,并返回 Promise 对象。当这些模块加载完成后,Promise才会被解析,您就可以在回调函数中使用这些模块
>./libs/main.js esri 地图初始化
import * as esriLoader from './esri-loader/esm/esri-loader.js';
export async function initMap(data) {
// require和function中的参数必须对应
console.log("🚀 ~ file: main.js:696 ~ initMap ~ window.$esriLocal:", window.$esriLocal)
// if (!esriLoader.isLoaded()) {
// esriLoader.loadScript({ url: "https://js.arcgis.com/3.44/" })
// esriLoader.loadCss("https://js.arcgis.com/3.44/esri/css/esri.css")
// 原来是require,改为loadModules,这样webpack打包时候就不会解析require,也不用在webpack.config.js external里面配置了。参考官方:
esriLoader.loadModules([
"esri/map",
"esri/Color",
"esri/graphic",
"esri/geometry/Extent",
"esri/geometry/Point",
"esri/geometry/Polygon",
"esri/geometry/Polyline",
"esri/SpatialReference",
"esri/layers/KMLLayer",
"esri/layers/GraphicsLayer",
"esri/symbols/SimpleMarkerSymbol",
"esri/symbols/FillSymbol",
"esri/symbols/LineSymbol",
"esri/symbols/SimpleFillSymbol",
"esri/symbols/SimpleLineSymbol",
"esri/symbols/TextSymbol",
"dojo/parser",
"esri/layers/TileInfo",
"esri/layers/WebTiledLayer",
"dojo/dom-style",
"esri/config"]).then(function ([
Map,
Color,
Graphic,
Extent,
Point,
Polygon,
Polyline,
SpatialReference,
KMLLayer,
GraphicsLayer,
SimpleMarkerSymbol,
FillSymbol,
LineSymbol,
SimpleFillSymbol,
SimpleLineSymbol,
TextSymbol,
parser, TileInfo, WebTiledLayer, domStyle,
esriConfig]) {
window.$esriLocal = {
Map: Map,
Color: Color,
SpatialReference: SpatialReference,
Extent: Extent,
WebTiledLayer: WebTiledLayer,
TileInfo: TileInfo,
Graphic: Graphic,
Point: Point,
Polyline: Polyline,
GraphicsLayer: GraphicsLayer,
SimpleMarkerSymbol: SimpleMarkerSymbol,
FillSymbol: FillSymbol,
LineSymbol: LineSymbol,
SimpleFillSymbol: SimpleFillSymbol,
SimpleLineSymbol: SimpleLineSymbol,
TextSymbol: TextSymbol,
}
// else {
console.log("🚀 ~ file: main.js:342 ~ initMap ~ window.$esriLocal:", window.$esriLocal)
/** 当前地图视角的显示矩形范围*/
let extentp = { west: 125.9738504337227, south: 61.624205573999646, east: 116.55005559052246, north: 38.67104274606849 };
let extent = new window.$esriLocal.Extent({
xmax: extentp.east,
xmin: extentp.west,
ymax: extentp.north,
ymin: extentp.south,
spatialReference: { wkid: 4490 },
});
map = new window.$esriLocal.Map("map", {
center: [],
zoom: 1,
fadeOnZoom: true,
fitExtent: true,
sliderPosition: "bottom-right",
logo: false,// 去除官方logo
// slider:false,
});
// 定位到范围
map.setExtent(extent);
// 在这里添加自定义底图
});
//}
// 老方法,但是这样在webpack中会影响打包,因为webpack默认使用commonjs模块系统进行打包,找不到对应模块和包就会报错。
/* await require([
"esri/map",
"esri/Color",
"esri/graphic",
"esri/geometry/Extent",
"esri/geometry/Point",
"esri/geometry/Polygon",
"esri/geometry/Polyline",
"esri/SpatialReference",
"esri/layers/KMLLayer",
"esri/layers/GraphicsLayer",
"esri/symbols/SimpleMarkerSymbol",
"esri/symbols/FillSymbol",
"esri/symbols/LineSymbol",
"esri/symbols/SimpleFillSymbol",
"esri/symbols/SimpleLineSymbol",
"esri/symbols/TextSymbol",
"dojo/parser",
"esri/layers/TileInfo",
"esri/layers/WebTiledLayer",
"dojo/dom-style",
"esri/config",
"dijit/layout/BorderContainer",
"dijit/layout/ContentPane",
], function (
Map, Color, Graphic, Extent, Point, Polygon, Polyline,
SpatialReference, KMLLayer, GraphicsLayer, SimpleMarkerSymbol,
FillSymbol,
LineSymbol,
SimpleFillSymbol,
SimpleLineSymbol,
TextSymbol,
parser, TileInfo, WebTiledLayer, domStyle,
esriConfig
) {
window.$esriLocal = {
Map: Map,
Color: Color,
SpatialReference: SpatialReference,
Extent: Extent,
WebTiledLayer: WebTiledLayer,
TileInfo: TileInfo,
Graphic: Graphic,
Point: Point,
Polyline: Polyline,
GraphicsLayer: GraphicsLayer,
SimpleMarkerSymbol: SimpleMarkerSymbol,
FillSymbol: FillSymbol,
LineSymbol: LineSymbol,
SimpleFillSymbol: SimpleFillSymbol,
SimpleLineSymbol: SimpleLineSymbol,
TextSymbol: TextSymbol,
}
// else {
console.log("🚀 ~ file: main.js:342 ~ initMap ~ window.$esriLocal:", window.$esriLocal)
/** 当前地图视角的显示矩形范围*
let extentp = { west: 125.9738504337227, south: 61.624205573999646, east: 116.55005559052246, north: 38.67104274606849 };
let extent = new window.$esriLocal.Extent({
xmax: extentp.east,
xmin: extentp.west,
ymax: extentp.north,
ymin: extentp.south,
spatialReference: { wkid: 4490 },
});
map = new window.$esriLocal.Map("map", {
center: [],
zoom: 1,
fadeOnZoom: true,
fitExtent: true,
sliderPosition: "bottom-right",
logo: false,// 去除官方logo
// slider:false,
});
// 定位到范围
map.setExtent(extent);
// }
}) */
}
缺点
也算优点吧,就是每次修改代码生效就需要用webpack的devServer进行了,不能用直接访问文件的形式。
注意
可能会出现multipleDefine的错误。就是引入init.js多次,引入一次就够了,还有网上说是jquery,但是我代码里面没用用jQuery,之前也出现了multipleDefine,但是设置这个之后就没有出现了,后面我又用html引入init.js,发现又没有报错,下次报错了再补充(主要没有发现哪里导致重复多次定义,init.js也就引用一次。
另一个猜想,是因为webpac之前有一篇文章说打包成amd,就是这个参数libraryTarget,然后在手机上出现multipleDefine,去除了重新打包就没有报错,这一快比较模糊。)
知识储备
解决这个问题需要的知识储备为
- JavaScript模块系统;
- webpack原理;
- webpack、arcgis js api使用经验;
总结
从原则、框架逻辑开始,也就是各家官方网站库使用教程开始,一般都有前言,包括背景介绍、库开发注意事项、如何使用(继承方式)
ArcGIS Maps SDK for JavaScript
webpack-getting-started
体系化知识的必要性。碎片化学习只在体系化学习之后。
你要学一个新东西,就要问这个东西是什么?历史是什么?怎么用?目前能用在哪里?基础内容都有什么?
没有具体老师的时候,官方就是最好的老师。
后记
webpack可以修改模块系统标识,所以有人一开始就使用AMD模块开发,修改难度比较难的话就用
libraryTarget: ‘amd’
但是我没时间测试,所以有人可以可以丢链接给我。