Skip to content
This repository was archived by the owner on Apr 7, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions resources/[system]/[builders]/webpack5/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.yarn.installed
node_modules/
13 changes: 13 additions & 0 deletions resources/[system]/[builders]/webpack5/fxmanifest.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- This resource is part of the default Cfx.re asset pack (cfx-server-data)
-- Altering or recreating for local use only is strongly discouraged.

version '1.0.0'
author 'Cfx.re <[email protected]>'
description 'Builds resources with webpack. To learn more: https://webpack.js.org'
repository 'https://github.com/citizenfx/cfx-server-data'

dependency 'yarn'
server_script 'webpack_builder.js'

fx_version 'adamant'
game 'common'
15 changes: 15 additions & 0 deletions resources/[system]/[builders]/webpack5/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "webpack5-builder",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"webpack": "^5.73.0",
"worker-farm": "^1.7.0"
}
}
174 changes: 174 additions & 0 deletions resources/[system]/[builders]/webpack5/webpack_builder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
const fs = require('fs');
const path = require('path');
const workerFarm = require('worker-farm');
let buildingInProgress = false;
let currentBuildingModule = '';

// some modules will not like the custom stack trace logic
const ops = Error.prepareStackTrace;
Error.prepareStackTrace = undefined;

const WEBPACK_CONFIG_KEY = 'webpack5_config';

const webpackBuildTask = {
shouldBuild(resourceName) {
const numMetaData = GetNumResourceMetadata(resourceName, WEBPACK_CONFIG_KEY);

if (numMetaData > 0) {
for (let i = 0; i < numMetaData; i++) {
const configName = GetResourceMetadata(resourceName, WEBPACK_CONFIG_KEY);

if (shouldBuild(configName)) {
return true;
}
}
}

return false;

function loadCache(config) {
const cachePath = `cache/${resourceName}/${config.replace(/\//g, '_')}.json`;

try {
return JSON.parse(fs.readFileSync(cachePath, {encoding: 'utf8'}));
} catch {
return null;
}
}

function shouldBuild(config) {
const cache = loadCache(config);

if (!cache) {
return true;
}

for (const file of cache) {
const stats = getStat(file.name);

if (!stats ||
stats.mtime !== file.stats.mtime ||
stats.size !== file.stats.size ||
stats.inode !== file.stats.inode) {
return true;
}
}

return false;
}

function getStat(path) {
try {
const stat = fs.statSync(path);

return stat ? {
mtime: stat.mtimeMs,
size: stat.size,
inode: stat.ino,
} : null;
} catch {
return null;
}
}
},

build(resourceName, cb) {
let buildWebpack = async () => {
let error = null;
const configs = [];
const promises = [];
const numMetaData = GetNumResourceMetadata(resourceName, WEBPACK_CONFIG_KEY);

for (let i = 0; i < numMetaData; i++) {
configs.push(GetResourceMetadata(resourceName, WEBPACK_CONFIG_KEY, i));
}

for (const configName of configs) {
const configPath = GetResourcePath(resourceName) + '/' + configName;

const cachePath = `cache/${resourceName}/${configName.replace(/\//g, '_')}.json`;

try {
fs.mkdirSync(path.dirname(cachePath));
} catch {
}

const config = require(configPath);

const workers = workerFarm(require.resolve('./webpack_runner'));

if (config) {
const resourcePath = path.resolve(GetResourcePath(resourceName));

while (buildingInProgress) {
console.log(`webpack is busy: we are waiting to compile ${resourceName} (${configName})`);
await sleep(3000);
}

console.log(`${resourceName}: started building ${configName}`);

buildingInProgress = true;
currentBuildingModule = resourceName;

promises.push(new Promise((resolve, reject) => {
workers({
configPath,
resourcePath,
cachePath
}, (err, outp) => {
workerFarm.end(workers);

if (err) {
console.error(err.stack || err);
if (err.details) {
console.error(err.details);
}

buildingInProgress = false;
currentBuildingModule = '';
currentBuildingScript = '';
reject("worker farm webpack errored out");
return;
}

if (outp.errors) {
for (const error of outp.errors) {
console.log(error);
}
buildingInProgress = false;
currentBuildingModule = '';
currentBuildingScript = '';
reject("webpack got an error");
return;
}

console.log(`${resourceName}: built ${configName}`);
buildingInProgress = false;
resolve();
});
}));
}
}

try {
await Promise.all(promises);
} catch (e) {
error = e.toString();
}

buildingInProgress = false;
currentBuildingModule = '';

if (error) {
cb(false, error);
} else cb(true);
};
buildWebpack().then();
}
};

function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

RegisterResourceBuildTaskFactory('z_webpack5', () => webpackBuildTask);
75 changes: 75 additions & 0 deletions resources/[system]/[builders]/webpack5/webpack_runner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const webpack = require('webpack');
const path = require('path');
const fs = require('fs');

function getStat(path) {
try {
const stat = fs.statSync(path);

return stat ? {
mtime: stat.mtimeMs,
size: stat.size,
inode: stat.ino,
} : null;
} catch {
return null;
}
}

class SaveStatePlugin {
constructor(inp) {
this.cache = [];
this.cachePath = inp.cachePath;
}

apply(compiler) {
compiler.hooks.afterCompile.tap('SaveStatePlugin', (compilation) => {
for (const file of compilation.fileDependencies) {
this.cache.push({
name: file,
stats: getStat(file)
});
}
});

compiler.hooks.done.tap('SaveStatePlugin', (stats) => {
if (stats.hasErrors()) {
return;
}

fs.writeFile(this.cachePath, JSON.stringify(this.cache), () => {

});
});
}
}

module.exports = (inp, callback) => {
const config = require(inp.configPath);

config.context = inp.resourcePath;

if (config.output && config.output.path) {
config.output.path = path.resolve(inp.resourcePath, config.output.path);
}

if (!config.plugins) {
config.plugins = [];
}

config.plugins.push(new SaveStatePlugin(inp));

webpack(config, (err, stats) => {
if (err) {
callback(err);
return;
}

if (stats.hasErrors()) {
callback(null, stats.toJson());
return;
}

callback(null, {});
});
};
Loading