diff --git a/templates/html5/npm/package.json b/templates/html5/npm/package.json new file mode 100755 index 000000000..2c7d7d992 --- /dev/null +++ b/templates/html5/npm/package.json @@ -0,0 +1,26 @@ +{ + "name": "::META_PACKAGE::", + "version": "::META_VERSION::", + "private": true, + "devDependencies": { + "haxe": "^5.0.10", + "haxe-loader": "^0.10.0", + "uglifyjs-webpack-plugin": "^1.3.0", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9", + "webpack-merge": "^4.1.4" + }, + "haxeDependencies": { + "haxe": "3.4.7" + }, + "scripts": { + "build": "npm run build:prod", + "build:dev": "webpack --config webpack.dev.js", + "build:prod": "webpack --config webpack.prod.js", + "start": "npm run start:dev", + "start:dev": "webpack-dev-server --open --config webpack.dev.js", + "start:prod": "webpack-dev-server --open --config webpack.prod.js" + }, + "dependencies": {} +} diff --git a/templates/html5/npm/webpack.common.js b/templates/html5/npm/webpack.common.js new file mode 100755 index 000000000..04152203f --- /dev/null +++ b/templates/html5/npm/webpack.common.js @@ -0,0 +1,20 @@ +const path = require ('path'); + +module.exports = { + entry: "./../haxe/::if DEBUG::debug.hxml::else::::if FINAL::final.hxml::else::release.hxml::end::::end::", + output: { + path: path.resolve (__dirname, "dist"), + filename: "::OUTPUT_FILE::", + library: "lime", + libraryTarget: 'window', + libraryExport: 'lime' + }, + module: { + rules: [ + { + test: /\.hxml$/, + loader: 'haxe-loader', + } + ] + } +}; \ No newline at end of file diff --git a/templates/html5/npm/webpack.dev.js b/templates/html5/npm/webpack.dev.js new file mode 100755 index 000000000..a770e2b88 --- /dev/null +++ b/templates/html5/npm/webpack.dev.js @@ -0,0 +1,10 @@ +const merge = require ('webpack-merge'); +const common = require ('./webpack.common.js'); + +module.exports = merge (common, { + mode: 'development', + devServer: { + contentBase: './dist' + }, + devtool: "inline-source-map", +}); \ No newline at end of file diff --git a/templates/html5/npm/webpack.prod.js b/templates/html5/npm/webpack.prod.js new file mode 100755 index 000000000..317b40fb2 --- /dev/null +++ b/templates/html5/npm/webpack.prod.js @@ -0,0 +1,17 @@ +const webpack = require ('webpack'); +const merge = require ('webpack-merge'); +const UglifyJSPlugin = require ('uglifyjs-webpack-plugin'); +const common = require ('./webpack.common.js'); + +module.exports = merge (common, { + mode: 'production', + devtool: "source-map", + plugins: [ + new UglifyJSPlugin ({ + sourceMap: true + }), + new webpack.DefinePlugin ({ + 'process.env.NODE_ENV': JSON.stringify ('production') + }) + ] +}); \ No newline at end of file diff --git a/tools/CommandLineTools.hx b/tools/CommandLineTools.hx index 6668e0fbb..890f26b0c 100644 --- a/tools/CommandLineTools.hx +++ b/tools/CommandLineTools.hx @@ -980,6 +980,7 @@ class CommandLineTools Log.println(" \x1b[3m(ios|tvos)\x1b[0m \x1b[1m-simulator\x1b[0m -- Target the device simulator"); Log.println(" \x1b[3m(ios)\x1b[0m \x1b[1m-simulator -ipad\x1b[0m -- Build/test for the iPad Simulator"); Log.println(" \x1b[3m(android)\x1b[0m \x1b[1m-emulator\x1b[0m -- Target the device emulator"); + Log.println(" \x1b[3m(html5)\x1b[0m \x1b[1m-npm\x1b[0m -- Target HTML5 using an NPM project structure"); Log.println(" \x1b[3m(flash)\x1b[0m \x1b[1m-web\x1b[0m -- Test Flash target using a web template"); Log.println(" \x1b[3m(air)\x1b[0m \x1b[1m-ios\x1b[0m -- Target iOS instead of AIR desktop"); Log.println(" \x1b[3m(air)\x1b[0m \x1b[1m-android\x1b[0m -- Target Android instead of AIR desktop"); diff --git a/tools/platforms/HTML5Platform.hx b/tools/platforms/HTML5Platform.hx index ed5c1825d..569e06161 100644 --- a/tools/platforms/HTML5Platform.hx +++ b/tools/platforms/HTML5Platform.hx @@ -25,6 +25,7 @@ import sys.FileSystem; class HTML5Platform extends PlatformTarget { private var dependencyPath:String; + private var npm:Bool; private var outputFile:String; public function new(command:String, _project:HXProject, targetFlags:Map) @@ -36,6 +37,18 @@ class HTML5Platform extends PlatformTarget public override function build():Void { + if (npm) + { + if (command == "build") + { + var buildCommand = "build:" + (project.targetFlags.exists("final") ? "prod" : "dev"); + System.runCommand(targetDirectory + "/bin", "npm", ["run", buildCommand, "-s"]); + } else + { + return; + } + } + ModuleHelper.buildModules(project, targetDirectory + "/obj", targetDirectory + "/bin"); if (project.app.main != null) @@ -76,9 +89,7 @@ class HTML5Platform extends PlatformTarget for (dependency in project.dependencies) { - if (dependency.embed - && StringTools.endsWith(dependency.path, ".js") - && FileSystem.exists(dependency.path)) + if (dependency.embed && StringTools.endsWith(dependency.path, ".js") && FileSystem.exists(dependency.path)) { var script = File.getContent(dependency.path); context.embeddedLibraries.push(script); @@ -163,18 +174,27 @@ class HTML5Platform extends PlatformTarget } dependencyPath = project.config.getString("html5.dependency-path", "lib"); - - if (targetFlags.exists("electron")) - { - dependencyPath = project.config.getString("html5.dependency-path", dependencyPath); - } - outputFile = targetDirectory + "/bin/" + project.app.file + ".js"; + + try + { + if (targetFlags.exists("npm") || (FileSystem.exists(targetDirectory + "/bin/package.json") && !targetFlags.exists("electron"))) + { + npm = true; + outputFile = project.app.file + ".js"; + } + } + catch (e:Dynamic) {} } public override function run():Void { - if (targetFlags.exists("electron")) + if (npm) + { + var runCommand = "start:" + (project.targetFlags.exists("final") ? "prod" : "dev"); + System.runCommand(targetDirectory + "/bin", "npm", ["run", runCommand, "-s"]); + } + else if (targetFlags.exists("electron")) { ElectronHelper.launch(project, targetDirectory + "/bin"); } @@ -191,6 +211,7 @@ class HTML5Platform extends PlatformTarget // project = project.clone (); var destination = targetDirectory + "/bin/"; + if (npm) destination += "dist/"; System.mkdir(destination); var webfontDirectory = targetDirectory + "/obj/webfont"; @@ -272,6 +293,14 @@ class HTML5Platform extends PlatformTarget } } + if (npm) + { + for (i in 0...project.sources.length) + { + project.sources[i] = Path.tryFullPath(project.sources[i]); + } + } + // for (library in libraryNames.keys ()) { // // project.haxeflags.push ("-resource " + targetDirectory + "/obj/manifest/" + library + ".json@__ASSET_MANIFEST__" + library); @@ -283,7 +312,7 @@ class HTML5Platform extends PlatformTarget var context = project.templateContext; context.WIN_FLASHBACKGROUND = project.window.background != null ? StringTools.hex(project.window.background, 6) : ""; - context.OUTPUT_DIR = targetDirectory; + context.OUTPUT_DIR = npm ? Path.tryFullPath(targetDirectory) : targetDirectory; context.OUTPUT_FILE = outputFile; if (project.targetFlags.exists("webgl")) @@ -315,7 +344,7 @@ class HTML5Platform extends PlatformTarget for (dependency in project.dependencies) { - if (!dependency.embed) + if (!dependency.embed || npm) { if (StringTools.endsWith(dependency.name, ".js")) { @@ -392,8 +421,8 @@ class HTML5Platform extends PlatformTarget if (hasFormat[1]) urls.push("url('" + embeddedAsset.targetPath + ".eot?#iefix') format('embedded-opentype')"); if (hasFormat[3]) urls.push("url('" + embeddedAsset.targetPath + ".woff') format('woff')"); urls.push("url('" + embeddedAsset.targetPath + ext + "') format('truetype')"); - if (hasFormat[2]) urls.push("url('" + embeddedAsset.targetPath + ".svg#" + StringTools.urlEncode(embeddedAsset - .fontName) + "') format('svg')"); + if (hasFormat[2]) urls.push("url('" + embeddedAsset.targetPath + ".svg#" + StringTools.urlEncode(embeddedAsset.fontName) + + "') format('svg')"); var fontFace = "\t\t@font-face {\n"; fontFace += "\t\t\tfont-family: '" + embeddedAsset.fontName + "';\n"; @@ -422,6 +451,15 @@ class HTML5Platform extends PlatformTarget ProjectHelper.recursiveSmartCopyTemplate(project, "html5/hxml", targetDirectory + "/haxe", context); } + if (npm) + { + ProjectHelper.recursiveSmartCopyTemplate(project, "html5/npm", targetDirectory + "/bin", context); + if (!FileSystem.exists(targetDirectory + "/bin/node_modules")) + { + System.runCommand(targetDirectory + "/bin", "npm", ["install", "-s"]); + } + } + if (targetFlags.exists("electron")) { ProjectHelper.recursiveSmartCopyTemplate(project, "electron/template", destination, context);