@playcraft/devkit 1.0.16 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +259 -0
- package/cli/bin/playable-scripts.js +44 -1
- package/cli/commands/build.js +122 -17
- package/cli/commands/builds.js +13 -2
- package/cli/commands/dev.js +7 -1
- package/cli/commands/pack.js +308 -39
- package/cli/commands/vite-dev.js +7 -1
- package/core/batch-build.js +1091 -124
- package/core/cos-uploader.js +264 -20
- package/core/index.js +58 -4
- package/core/loaders/gltf-loader.js +74 -28
- package/core/options.js +310 -15
- package/core/plugins/AdikteevInjectorPlugin.js +26 -4
- package/core/plugins/BigoAdsInjectorPlugin.js +21 -4
- package/core/plugins/DAPIInjectorPlugin.js +25 -2
- package/core/plugins/DebuggerInjectionPlugin.js +31 -3
- package/core/plugins/ExitAPIInjectorPlugin.js +57 -3
- package/core/plugins/FflateCompressionPlugin.js +225 -37
- package/core/plugins/LiftoffInjectorPlugin.js +26 -4
- package/core/plugins/MRAIDInjectorPlugin.js +24 -2
- package/core/plugins/MintegralInjectorPlugin.js +25 -2
- package/core/plugins/PangleInjectorPlugin.js +24 -2
- package/core/plugins/SnapchatInjectorPlugin.js +26 -4
- package/core/plugins/TikTokInjectorPlugin.js +24 -2
- package/core/plugins/UnityInjectorPlugin.js +37 -8
- package/core/plugins/ZipPlugin.js +138 -19
- package/core/utils/buildDefines.js +29 -2
- package/core/utils/buildTemplateString.js +40 -5
- package/core/utils/date.js +16 -1
- package/core/utils/generateAdikteevHtmlWebpackPluginConfig.js +42 -7
- package/core/utils/generateBigabidHtmlWebpackPluginConfig.js +30 -3
- package/core/utils/generateInMobiHtmlWebpackPluginConfig.js +43 -7
- package/core/utils/injectSDKPlugins.js +58 -11
- package/core/utils/logOptions.js +37 -4
- package/core/utils/mergeOptions.js +19 -2
- package/core/utils/parseArgvOptions.js +110 -5
- package/core/utils/resolveChannelFold2Zip.js +15 -3
- package/core/utils/validateThemeData.js +220 -25
- package/core/validators/pre-build-checker.js +201 -21
- package/core/validators/tracking-validator.js +358 -40
- package/core/vite.dev.js +181 -15
- package/core/webpack.build.js +446 -52
- package/core/webpack.common.js +177 -11
- package/core/webpack.dev.js +82 -5
- package/package.json +3 -2
package/core/options.js
CHANGED
|
@@ -1,6 +1,230 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const {
|
|
2
|
+
parseArgvOptions,
|
|
3
|
+
allowedAdProtocols,
|
|
4
|
+
allowedAdNetworks,
|
|
5
|
+
allowedOrientations,
|
|
6
|
+
allowedLanguages
|
|
7
|
+
} = require('./utils/parseArgvOptions');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const { name, version } = require('../package.json');
|
|
11
|
+
|
|
12
|
+
const possibleOptions = [
|
|
13
|
+
{
|
|
14
|
+
name: 'out-dir',
|
|
15
|
+
alias: 'outDir',
|
|
16
|
+
defaultValue: 'dist',
|
|
17
|
+
hasValue: true,
|
|
18
|
+
description: 'Output directory for build files'
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'build-config',
|
|
22
|
+
alias: 'buildConfig',
|
|
23
|
+
defaultValue: 'build.json',
|
|
24
|
+
hasValue: true,
|
|
25
|
+
description: 'Path to build.json configuration file'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'ts-config',
|
|
29
|
+
alias: 'tsConfig',
|
|
30
|
+
defaultValue: 'tsconfig.json',
|
|
31
|
+
hasValue: true,
|
|
32
|
+
description: 'For TypeScript projects, path to tsconfig.json file'
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'js-config',
|
|
36
|
+
alias: 'jsConfig',
|
|
37
|
+
defaultValue: 'jsconfig.json',
|
|
38
|
+
hasValue: true,
|
|
39
|
+
description: 'For JavaScript projects, path to jsconfig.json file'
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'port',
|
|
43
|
+
hasValue: true,
|
|
44
|
+
defaultValue: 3000,
|
|
45
|
+
description: 'Development server port number',
|
|
46
|
+
parser: function (rawValue) {
|
|
47
|
+
const value = +rawValue;
|
|
48
|
+
if (isNaN(value)) throw new Error('--port should be a number');
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'open',
|
|
54
|
+
defaultValue: false,
|
|
55
|
+
hasValue: false,
|
|
56
|
+
description: 'Open browser automatically when server starts'
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'protocol',
|
|
60
|
+
hasValue: true,
|
|
61
|
+
defaultValue: 'none',
|
|
62
|
+
description: 'Ad protocol to use (none, mraid, or dapi)',
|
|
63
|
+
parser: function (rawValue) {
|
|
64
|
+
if (!allowedAdProtocols.includes(rawValue)) {
|
|
65
|
+
throw new Error(`--protocol should have one of the value: ${allowedAdProtocols.join(', ')}`);
|
|
66
|
+
}
|
|
67
|
+
return rawValue;
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'network',
|
|
72
|
+
defaultValue: 'preview',
|
|
73
|
+
hasValue: true,
|
|
74
|
+
description: 'Target Ad network',
|
|
75
|
+
parser: function (rawValue) {
|
|
76
|
+
if (!allowedAdNetworks.includes(rawValue)) {
|
|
77
|
+
throw new Error(`--network should have one of the value: ${allowedAdNetworks.join(', ')}`);
|
|
78
|
+
}
|
|
79
|
+
return rawValue;
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'zip',
|
|
84
|
+
defaultValue: false,
|
|
85
|
+
hasValue: false,
|
|
86
|
+
description: 'Should the build be zipped? (only for some ad networks)'
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'is-channel-fold2zip',
|
|
90
|
+
defaultValue: false,
|
|
91
|
+
hasValue: false,
|
|
92
|
+
description: '当启用时,构建产物格式为「主题文件名/渠道.zip」'
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'dev',
|
|
96
|
+
hasValue: true,
|
|
97
|
+
description: 'Enable development mode (true/false)',
|
|
98
|
+
parser: function (rawValue) {
|
|
99
|
+
if (!(rawValue === 'true' || rawValue === 'false')) throw new Error('--dev should have either true or false value');
|
|
100
|
+
return rawValue === 'true';
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'filename',
|
|
105
|
+
defaultValue: '{app}_{name}_{version}_{date}_{language}_{network}',
|
|
106
|
+
hasValue: true,
|
|
107
|
+
description: 'Specifies the build filename template'
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'app',
|
|
111
|
+
defaultValue: 'AppName',
|
|
112
|
+
hasValue: true,
|
|
113
|
+
description: 'Specifies the application name used in build filename and APP define'
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'name',
|
|
117
|
+
defaultValue: 'ConceptName',
|
|
118
|
+
hasValue: true,
|
|
119
|
+
description: 'Specifies the concept name used in build filename and NAME define'
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: 'version',
|
|
123
|
+
defaultValue: 'v1',
|
|
124
|
+
hasValue: true,
|
|
125
|
+
description: 'Specifies the version name used in build filename and VERSION define'
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'language',
|
|
129
|
+
defaultValue: 'en',
|
|
130
|
+
hasValue: true,
|
|
131
|
+
description: 'Specifies the language of the build used in LANGUAGE define',
|
|
132
|
+
parser: function (rawValue) {
|
|
133
|
+
if (!allowedLanguages.includes(rawValue)) {
|
|
134
|
+
throw new Error(`--platform should have one of the value: ${allowedLanguages.join(', ')}`);
|
|
135
|
+
}
|
|
136
|
+
return rawValue;
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: 'orientation',
|
|
141
|
+
defaultValue: 'both',
|
|
142
|
+
hasValue: true,
|
|
143
|
+
description: 'Specifies the ad orientation used in ORIENTATION define and some network specific configurations',
|
|
144
|
+
parser: function (rawValue) {
|
|
145
|
+
if (!allowedOrientations.includes(rawValue)) {
|
|
146
|
+
throw new Error(`--orientation should have one of the value: ${allowedOrientations.join(', ')}`);
|
|
147
|
+
}
|
|
148
|
+
return rawValue;
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: 'google-play-url',
|
|
153
|
+
alias: 'googlePlayUrl',
|
|
154
|
+
defaultValue: 'https://play.google.com/store/games',
|
|
155
|
+
hasValue: true,
|
|
156
|
+
description: 'Google Play Store URL for the app'
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'app-store-url',
|
|
160
|
+
alias: 'appStoreUrl',
|
|
161
|
+
defaultValue: 'https://www.apple.com/app-store/',
|
|
162
|
+
hasValue: true,
|
|
163
|
+
description: 'App Store URL for the app'
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: 'skip-recommended-meta',
|
|
167
|
+
alias: 'skipRecommendedMeta',
|
|
168
|
+
hasValue: false,
|
|
169
|
+
description: "Don't inject recommended for playable ads META tags"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: 'debugger',
|
|
173
|
+
hasValue: true,
|
|
174
|
+
description: 'URL of debugger script to inject into code'
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: 'obfuscate-level',
|
|
178
|
+
alias: 'obfuscateLevel',
|
|
179
|
+
defaultValue: 4,
|
|
180
|
+
hasValue: true,
|
|
181
|
+
description: 'Code obfuscation level: 0=no minimize/obfuscate, 1=terser only, 2=obfuscator basic(default), 3=obfuscator+dead code, 4=maximum obfuscation',
|
|
182
|
+
parser: function (rawValue) {
|
|
183
|
+
const value = +rawValue;
|
|
184
|
+
if (isNaN(value) || value < 0 || value > 4) {
|
|
185
|
+
throw new Error('--obfuscate-level should be a number between 0 and 4');
|
|
186
|
+
}
|
|
187
|
+
return value;
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: 'fflate-compression',
|
|
192
|
+
alias: 'fflateCompression',
|
|
193
|
+
defaultValue: false,
|
|
194
|
+
hasValue: true,
|
|
195
|
+
description: 'Enable fflate compression for HTML output (level 9, production only)',
|
|
196
|
+
parser: function (rawValue) {
|
|
197
|
+
// 支持 true/false 字符串和布尔值
|
|
198
|
+
if (rawValue === 'true' || rawValue === true || rawValue === '1') return true;
|
|
199
|
+
if (rawValue === 'false' || rawValue === false || rawValue === '0') return false;
|
|
200
|
+
return Boolean(rawValue);
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: 'exclude-compress-dirs',
|
|
205
|
+
alias: 'excludeCompressDirs',
|
|
206
|
+
defaultValue: [],
|
|
207
|
+
hasValue: true,
|
|
208
|
+
description: 'Directories to exclude from compression (comma-separated list of paths)',
|
|
209
|
+
parser: function (rawValue) {
|
|
210
|
+
if (!rawValue) return [];
|
|
211
|
+
return rawValue.split(',').map(dir => dir.trim()).filter(Boolean);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
/** @type {import('./index').CLIOptions} */
|
|
217
|
+
const options = parseArgvOptions(possibleOptions);
|
|
218
|
+
|
|
219
|
+
// Extract CLI explicit keys set, then remove it from options
|
|
220
|
+
const cliExplicitKeys = options._cliExplicitKeys || new Set();
|
|
221
|
+
delete options._cliExplicitKeys;
|
|
222
|
+
|
|
223
|
+
options.defines = {};
|
|
224
|
+
options.compilation = {};
|
|
225
|
+
options.adNetworkNames = {};
|
|
226
|
+
|
|
227
|
+
// ==================== 配置文件加载 ====================
|
|
4
228
|
//
|
|
5
229
|
// 优先级(从低到高):
|
|
6
230
|
// 1. builds.config.js (新配置文件,优先读取)
|
|
@@ -10,17 +234,88 @@ const cliExplicitKeys=options._cliExplicitKeys||new Set;delete options._cliExpli
|
|
|
10
234
|
// 字段映射:
|
|
11
235
|
// - builds.config.js: googlePlayUrl, appStoreUrl
|
|
12
236
|
// - build.json: google_play_url → googlePlayUrl, app_store_url → appStoreUrl
|
|
237
|
+
|
|
13
238
|
// 1️⃣ 先尝试读取 builds.config.js
|
|
14
|
-
const buildsConfigPath=path.resolve(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
239
|
+
const buildsConfigPath = path.resolve('builds.config.js');
|
|
240
|
+
if (fs.existsSync(buildsConfigPath)) {
|
|
241
|
+
try {
|
|
242
|
+
// 清除 require 缓存,确保读取最新配置
|
|
243
|
+
delete require.cache[buildsConfigPath];
|
|
244
|
+
const buildsConfig = require(buildsConfigPath);
|
|
245
|
+
|
|
246
|
+
// 读取商店链接配置(respect CLI args priority)
|
|
247
|
+
if (buildsConfig.googlePlayUrl && !cliExplicitKeys.has('googlePlayUrl')) {
|
|
248
|
+
options.googlePlayUrl = buildsConfig.googlePlayUrl;
|
|
249
|
+
}
|
|
250
|
+
if (buildsConfig.appStoreUrl && !cliExplicitKeys.has('appStoreUrl')) {
|
|
251
|
+
options.appStoreUrl = buildsConfig.appStoreUrl;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// 读取其他特殊配置
|
|
255
|
+
if (buildsConfig.defines) Object.assign(options.defines, buildsConfig.defines);
|
|
256
|
+
if (buildsConfig.compilation) Object.assign(options.compilation, buildsConfig.compilation);
|
|
257
|
+
if (buildsConfig.adNetworkNames) Object.assign(options.adNetworkNames, buildsConfig.adNetworkNames);
|
|
258
|
+
|
|
259
|
+
// 读取所有通用配置字段(如果在 builds.config.js 中定义了)
|
|
260
|
+
for (const key in buildsConfig) {
|
|
261
|
+
// 跳过已处理的特殊字段和批量构建专用字段
|
|
262
|
+
if (['googlePlayUrl', 'appStoreUrl', 'defines', 'compilation', 'adNetworkNames',
|
|
263
|
+
'title', 'appName', 'projectCode', 'channels', 'themes', 'naming', 'build', 'hooks', 'cosConfig'].includes(key)) {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 查找对应的配置项定义
|
|
268
|
+
const possibleOption = possibleOptions.find((e) => e.alias === key || e.name === key);
|
|
269
|
+
if (possibleOption && buildsConfig[key] !== undefined) {
|
|
270
|
+
// Only override if not explicitly set by CLI args
|
|
271
|
+
const optionKey = possibleOption.alias || possibleOption.name;
|
|
272
|
+
if (!cliExplicitKeys.has(optionKey)) {
|
|
273
|
+
options[optionKey] = buildsConfig[key];
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
} catch (err) {
|
|
279
|
+
console.log('\x1b[33m⚠️ 读取 builds.config.js 失败: ' + err.message + '\x1b[0m');
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// 2️⃣ 再尝试读取 build.json(会覆盖 builds.config.js 中的相同字段)
|
|
284
|
+
try {
|
|
285
|
+
const fileData = fs.readFileSync(path.resolve(options['buildConfig']), 'utf8');
|
|
286
|
+
try {
|
|
287
|
+
const customOptions = JSON.parse(fileData);
|
|
288
|
+
for (let key in customOptions) {
|
|
289
|
+
if (key === 'defines') Object.assign(options.defines, customOptions[key]);
|
|
290
|
+
else if (key === 'compilation') Object.assign(options.compilation, customOptions[key]);
|
|
291
|
+
else if (key === 'adNetworkNames') Object.assign(options.adNetworkNames, customOptions[key]);
|
|
292
|
+
else if (key === 'google_play_url' && !cliExplicitKeys.has('googlePlayUrl')) options.googlePlayUrl = customOptions.google_play_url;
|
|
293
|
+
else if (key === 'app_store_url' && !cliExplicitKeys.has('appStoreUrl')) options.appStoreUrl = customOptions.app_store_url;
|
|
294
|
+
else {
|
|
295
|
+
const possibleOption = possibleOptions.find((e) => e.alias === key || e.name === key);
|
|
296
|
+
if (possibleOption) {
|
|
297
|
+
const optionKey = possibleOption.alias || possibleOption.name;
|
|
298
|
+
// Only override if not explicitly set by CLI args
|
|
299
|
+
if (!cliExplicitKeys.has(optionKey)) {
|
|
300
|
+
options[optionKey] = customOptions[key];
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
} catch (err) {
|
|
306
|
+
console.log('\x1b[31mBuild config parsing error: ' + err.message + '\x1b[0m');
|
|
307
|
+
}
|
|
308
|
+
} catch (err) {
|
|
309
|
+
// build.json 不存在是正常的(使用 builds.config.js)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// 环境变量覆盖(用于批量构建时避免竞态条件)
|
|
25
313
|
// 批量构建时通过 PLAYABLE_FILENAME 环境变量传递 filename,优先级最高
|
|
26
|
-
if(process.env.PLAYABLE_FILENAME)
|
|
314
|
+
if (process.env.PLAYABLE_FILENAME) {
|
|
315
|
+
options.filename = process.env.PLAYABLE_FILENAME;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
console.log(`${name} v${version}`);
|
|
319
|
+
console.log('Options:', options)
|
|
320
|
+
|
|
321
|
+
exports.options = options;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
2
|
+
|
|
3
|
+
/**
|
|
2
4
|
* AdikteevInjectorPlugin
|
|
3
5
|
*
|
|
4
6
|
* 在开发环境中注入 Adikteev MRAID mock 和占位符变量
|
|
@@ -9,8 +11,17 @@
|
|
|
9
11
|
* 1. 使用 mraid.open() 打开目标 URL
|
|
10
12
|
* 2. 暴露 AK_CLICK_DESTINATION_URL 和 AK_CLICK_PIXEL_URL 占位符
|
|
11
13
|
* 3. 生产环境由 Adikteev CDN 提供 mraid.js
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
+
*/
|
|
15
|
+
class AdikteevInjectorPlugin {
|
|
16
|
+
apply(compiler) {
|
|
17
|
+
compiler.hooks.compilation.tap('AdikteevInjectorPlugin', (compilation) => {
|
|
18
|
+
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync(
|
|
19
|
+
'AdikteevInjectorPlugin',
|
|
20
|
+
(data, cb) => {
|
|
21
|
+
// 注入 MRAID mock (仅开发环境)
|
|
22
|
+
data.headTags.unshift({
|
|
23
|
+
tagName: 'script',
|
|
24
|
+
innerHTML: `
|
|
14
25
|
// Adikteev MRAID Mock (Development Only)
|
|
15
26
|
if (typeof mraid === 'undefined') {
|
|
16
27
|
window.mraid = {
|
|
@@ -44,4 +55,15 @@ data.headTags.unshift({tagName:"script",innerHTML:`
|
|
|
44
55
|
window.AK_CLICK_PIXEL_URL = "";
|
|
45
56
|
console.log('[Adikteev] AK_CLICK_PIXEL_URL mock set (empty for dev)');
|
|
46
57
|
}
|
|
47
|
-
`,
|
|
58
|
+
`,
|
|
59
|
+
voidTag: false
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
cb(null, data);
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
exports.AdikteevInjectorPlugin = AdikteevInjectorPlugin;
|
|
@@ -1,7 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
2
|
+
|
|
3
|
+
/**
|
|
2
4
|
* BigoAds SDK 注入插件
|
|
3
5
|
* 在 HTML 头部注入 BigoAds 专用 SDK 脚本
|
|
4
6
|
* 文档: https://www.bigoads.com/help/detail?id=143&moduleId=7¤tLan=CN
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
*/
|
|
8
|
+
class BigoAdsInjectorPlugin {
|
|
9
|
+
apply(compiler) {
|
|
10
|
+
compiler.hooks.compilation.tap('BigoAdsInjectorPlugin', (compilation) => {
|
|
11
|
+
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync('BigoAdsInjectorPlugin', (data, cb) => {
|
|
12
|
+
// BigoAds 要求必须引入此 SDK,不允许使用标准 mraid.js
|
|
13
|
+
const bigoAdsScript = `<script src="https://static-web.likeevideo.com/as/common-static/big-data/dsp-public/bgy-mraid-sdk.js"></script>`;
|
|
14
|
+
|
|
15
|
+
// 在 <head> 标签后注入
|
|
16
|
+
data.html = data.html.replace('<head>', `<head>\n ${bigoAdsScript}`);
|
|
17
|
+
|
|
18
|
+
cb(null, data);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
exports.BigoAdsInjectorPlugin = BigoAdsInjectorPlugin;
|
|
@@ -1,5 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
2
|
+
|
|
3
|
+
const dapiSrc =
|
|
4
|
+
'function getScript(e,i){var n=document.createElement("script");n.type="text/javascript",n.async=!0,i&&(n.onload=i),n.src=e,document.head.appendChild(n)}function parseMessage(e){var i=e.data,n=i.indexOf(DOLLAR_PREFIX+RECEIVE_MSG_PREFIX);if(-1!==n){var t=i.slice(n+2);return getMessageParams(t)}return{}}function getMessageParams(e){var i,n=[],t=e.split("/"),a=t.length;if(-1===e.indexOf(RECEIVE_MSG_PREFIX)){if(a>=2&&a%2===0)for(i=0;a>i;i+=2)n[t[i]]=t.length<i+1?null:decodeURIComponent(t[i+1])}else{var o=e.split(RECEIVE_MSG_PREFIX);void 0!==o[1]&&(n=JSON&&JSON.parse(o[1]))}return n}function getDapi(e){var i=parseMessage(e);if(!i||i.name===GET_DAPI_URL_MSG_NAME){var n=i.data;getScript(n,onDapiReceived)}}function invokeDapiListeners(){for(var e in dapiEventsPool)dapiEventsPool.hasOwnProperty(e)&&dapi.addEventListener(e,dapiEventsPool[e])}function onDapiReceived(){dapi=window.dapi,window.removeEventListener("message",getDapi),invokeDapiListeners()}function init(){window.dapi.isDemoDapi&&(window.parent.postMessage(DOLLAR_PREFIX+SEND_MSG_PREFIX+JSON.stringify({state:"getDapiUrl"}),"*"),window.addEventListener("message",getDapi,!1))}var DOLLAR_PREFIX="$$",RECEIVE_MSG_PREFIX="DAPI_SERVICE:",SEND_MSG_PREFIX="DAPI_AD:",GET_DAPI_URL_MSG_NAME="connection.getDapiUrl",dapiEventsPool={},dapi=window.dapi||{isReady:function(){return!1},addEventListener:function(e,i){dapiEventsPool[e]=i},removeEventListener:function(e){delete dapiEventsPool[e]},isDemoDapi:!0};init();';
|
|
5
|
+
|
|
6
|
+
/**
|
|
2
7
|
* Webpack plugin that injects DAPI (Display Advertising Programming Interface) script
|
|
3
8
|
* into the HTML head. This enables communication with ad networks that use DAPI protocol.
|
|
4
9
|
* @implements {import('webpack').WebpackPluginInstance}
|
|
5
|
-
*/
|
|
10
|
+
*/
|
|
11
|
+
class DAPIInjectorPlugin {
|
|
12
|
+
apply(compiler) {
|
|
13
|
+
compiler.hooks.compilation.tap('DAPIInjectorPlugin', (compilation) => {
|
|
14
|
+
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync('DAPIInjectorPlugin', (data, callback) => {
|
|
15
|
+
data.headTags.splice(0, 0, {
|
|
16
|
+
tagName: 'script',
|
|
17
|
+
voidTag: false,
|
|
18
|
+
meta: { plugin: 'html-inline-script-webpack-plugin' },
|
|
19
|
+
innerHTML: dapiSrc
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
callback(null, data);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
exports.DAPIInjectorPlugin = DAPIInjectorPlugin;
|
|
@@ -1,6 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
2
|
+
|
|
3
|
+
/**
|
|
2
4
|
* Webpack plugin that injects a custom debugger script into the HTML head
|
|
3
5
|
* @implements {import('webpack').WebpackPluginInstance}
|
|
4
|
-
*/
|
|
6
|
+
*/
|
|
7
|
+
class DebuggerInjectionPlugin {
|
|
8
|
+
/**
|
|
5
9
|
* @param {string} debuggerSrc - URL of the debugger script to inject
|
|
6
|
-
*/
|
|
10
|
+
*/
|
|
11
|
+
constructor(debuggerSrc) {
|
|
12
|
+
this.debuggerSrc = debuggerSrc;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
apply(compiler) {
|
|
16
|
+
compiler.hooks.compilation.tap('DebuggerInjectionPlugin', (compilation) => {
|
|
17
|
+
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync('DebuggerInjectionPlugin', (data, callback) => {
|
|
18
|
+
data.headTags.splice(0, 0, {
|
|
19
|
+
tagName: 'script',
|
|
20
|
+
voidTag: false,
|
|
21
|
+
meta: { plugin: 'html-inline-script-webpack-plugin' },
|
|
22
|
+
attributes: {
|
|
23
|
+
src: this.debuggerSrc,
|
|
24
|
+
type: 'text/javascript'
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
callback(null, data);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
exports.DebuggerInjectionPlugin = DebuggerInjectionPlugin;
|
|
@@ -1,10 +1,64 @@
|
|
|
1
|
-
|
|
1
|
+
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
2
|
+
|
|
3
|
+
/**
|
|
2
4
|
* Webpack plugin that injects Google's ExitAPI script and required meta tags for Google Ads.
|
|
3
5
|
* Adds:
|
|
4
6
|
* - ExitAPI script for handling ad exits
|
|
5
7
|
* - Meta tag for ad size configuration
|
|
6
8
|
* - Meta tag for ad orientation support
|
|
7
9
|
* @implements {import('webpack').WebpackPluginInstance}
|
|
8
|
-
*/
|
|
10
|
+
*/
|
|
11
|
+
class ExitAPIInjectorPlugin {
|
|
12
|
+
/**
|
|
9
13
|
* @param {ORIENTATION} orientation - device orientation
|
|
10
|
-
*/
|
|
14
|
+
*/
|
|
15
|
+
constructor(orientation) {
|
|
16
|
+
this.orientation = orientation;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
apply(compiler) {
|
|
20
|
+
compiler.hooks.compilation.tap('ExitAPIInjectorPlugin', (compilation) => {
|
|
21
|
+
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync('ExitAPIInjectorPlugin', (data, callback) => {
|
|
22
|
+
data.headTags.splice(0, 0, {
|
|
23
|
+
tagName: 'script',
|
|
24
|
+
voidTag: false,
|
|
25
|
+
meta: { plugin: 'html-inline-script-webpack-plugin' },
|
|
26
|
+
attributes: {
|
|
27
|
+
src: 'https://tpc.googlesyndication.com/pagead/gadgets/html5/api/exitapi.js',
|
|
28
|
+
type: 'text/javascript'
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
let width = 320;
|
|
33
|
+
let height = 480;
|
|
34
|
+
|
|
35
|
+
if (this.orientation === 'square') width = 480;
|
|
36
|
+
else if (this.orientation === 'landscape') {
|
|
37
|
+
width = 480;
|
|
38
|
+
height = 320;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
data.headTags.splice(0, 0, {
|
|
42
|
+
tagName: 'meta',
|
|
43
|
+
voidTag: true,
|
|
44
|
+
meta: { plugin: 'html-webpack-plugin' },
|
|
45
|
+
attributes: { name: 'ad.size', content: `width=${width},height=${height}` }
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
data.headTags.splice(0, 0, {
|
|
49
|
+
tagName: 'meta',
|
|
50
|
+
voidTag: true,
|
|
51
|
+
meta: { plugin: 'html-webpack-plugin' },
|
|
52
|
+
attributes: {
|
|
53
|
+
name: 'ad.orientation',
|
|
54
|
+
content: this.orientation === 'both' || this.orientation === 'square' ? 'portrait,landscape' : this.orientation
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
callback(null, data);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
exports.ExitAPIInjectorPlugin = ExitAPIInjectorPlugin;
|