1 /*
  2  * WebpackLoader.js - Loader implementation for webpack'ed ilib on the web
  3  *
  4  * Copyright © 2018, 2020 JEDLSoft
  5  *
  6  * Licensed under the Apache License, Version 2.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at
  9  *
 10  *     http://www.apache.org/licenses/LICENSE-2.0
 11  *
 12  * Unless required by applicable law or agreed to in writing, software
 13  * distributed under the License is distributed on an "AS IS" BASIS,
 14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  *
 16  * See the License for the specific language governing permissions and
 17  * limitations under the License.
 18  */
 19 
 20 var Loader = require("./Loader.js");
 21 var Path = require("./Path.js");
 22 var Locale = require("./Locale.js");
 23 var ISet = require("./ISet.js");
 24 var Utils = require("./Utils.js");
 25 var JSUtils = require("./JSUtils.js");
 26 
 27 var alreadyLoaded = new ISet();
 28 
 29 function loadLocaleData(ilib, locale, callback) {
 30     switch (locale) {
 31         // This special macro will get replaced in the ilibdata-webpack-loader with
 32         // case statements for each locale js file so that webpack can make separate
 33         // bundles for each one of those files when in dynamic load mode. In static
 34         // (assembled) mode, this will get replaced with require calls that cause
 35         // the data to be put into one giant webpack bundle.
 36 
 37         // !loadLocaleData
 38 
 39         case "dummy":
 40             // This is just here to prevent webpack from removing the function contents. Otherwise,
 41             // it will think this whole function is "unused" or "dead" code.
 42             console.log("Should never happen");
 43             callback();
 44             break;
 45     }
 46 }
 47 
 48 /**
 49  * @class
 50  * Implementation of Loader for webpack.
 51  *
 52  * @constructor
 53  * @private
 54  */
 55 module.exports = function (ilib) {
 56     var WebpackLoader = function (ilib) {
 57         // console.log("new common WebpackLoader instance");
 58 
 59         this.parent.call(this, ilib);
 60         this.ilib = ilib;
 61         this.manifests = {};
 62 
 63         this.includePath.push("");
 64     };
 65 
 66     // make this a subclass of loader
 67     WebpackLoader.prototype = new Loader();
 68     WebpackLoader.prototype.parent = Loader;
 69     WebpackLoader.prototype.constructor = WebpackLoader;
 70 
 71     WebpackLoader.prototype.name = "WebpackLoader";
 72 
 73     WebpackLoader.prototype.getData = function(dataName, tzName) {
 74         return dataName === "zoneinfo" ? this.ilib.data.zoneinfo[tzName] : this.ilib.data[dataName];
 75     };
 76 
 77     // used locally with node
 78     WebpackLoader.prototype._loadFile = function (pathname, sync, cb) {
 79         var dir = Path.dirname(pathname);
 80         var base = Path.basename(pathname, "json");
 81         var dataName = base;
 82         var locale = undefined;
 83         var filename;
 84         var tzName;
 85 
 86         if (dir) {
 87             var locpath = /(^|\/)([a-z][a-z][a-z]?(\/[A-Z][a-z][a-z][a-z])?(\/[A-Z][A-Z](\/[A-Z]+)?)?)$/.exec(dir);
 88             if (locpath) {
 89                 locale = new Locale(locpath[2].replace(/\//g, "-"));
 90             }
 91         }
 92 
 93         filename = locale && locale.getSpec() || (base === "ilibmanifest" ? "localmanifest" : "root");
 94 
 95         dataName = base;
 96         if (dir) {
 97             if (locale) {
 98                 dataName += "_" + filename;
 99             } else if (dir === "charset" || dir === "charmaps") {
100                 dataName = dir + "_" + base;
101             } else if (dir.substring(0, 9) === "zoneinfo/") {
102                 dataName = "zoneinfo";
103                 tzName = pathname.substring(9).substring(0, - 5);
104             }
105         }
106 
107         dataName = dataName.replace(/[\.:\(\)\/\\\+\-]/g, "_");
108 
109         var data = this.getData(dataName, tzName);
110 
111         if (data) {
112             // already loaded
113             if (typeof(cb) === "function") cb(data);
114         } else if (!alreadyLoaded.has(filename)) {
115             console.log("WebpackLoader._loadFile: loading " + pathname + (sync ? " sync" : " async") + " as " + filename + ".js");
116 
117             alreadyLoaded.add(filename);
118             loadLocaleData(this.ilib, filename, function(callback, data) {
119                 data = (data && typeof(data) === "object" && typeof(data.installLocale) === "function") ? this.getData(dataName, tzName) : data;
120                 if (callback && typeof(callback) === "function") {
121                     callback(data);
122                 }
123             }.bind(this, cb));
124         } else {
125             if (typeof(cb) === "function") cb();
126         }
127 
128         // return data;
129     };
130 
131     WebpackLoader.prototype._exists = function(dir, file) {
132         return false;
133     };
134 
135     // used remotely on a browser
136     WebpackLoader.prototype._loadLocaleFile = function(path, sync, callback) {
137         var base = Path.basename(path, ".js");
138 
139         if (alreadyLoaded.has(base)) {
140             callback();
141         } else {
142             console.log("WebpackLoader._loadLocaleFile: loading " + path + (sync ? " sync" : " async"));
143 
144             alreadyLoaded.add(base);
145             loadLocaleData(this.ilib, base, function(callback, data) {
146                 callback(data);
147             }.bind(this, callback));
148         }
149     };
150 
151     /**
152      * @private
153      */
154     WebpackLoader.prototype._ensureManifest = function(locale, dir, callback) {
155         if (this.manifests[dir]) {
156             callback(this.manifests[dir]);
157         } else {
158             this._loadLocaleFile(Path.join(dir, "remotemanifest.js"), false, ilib.bind(this, function(manifest) {
159                 if (manifest) {
160                     this.manifests[dir] = manifest.files;
161                     callback(manifest.files);
162                 } else {
163                     callback(); // undefined param indicates error
164                 }
165             }));
166         }
167     };
168 
169     /**
170      * Ensure that the data for a locale is loaded into memory from the given
171      * dir. This will decompose the locale into its constituent parts and
172      * load all the appropriate files based on those parts. Thereafter,
173      * all ilib code can be called synchronously.
174      */
175     WebpackLoader.prototype.ensureLocale = function(locale, dir, callback) {
176         this._ensureManifest(locale, dir, ilib.bind(this, function(manifest) {
177             var filesToLoad = Utils.getSublocales(locale).map(function(sublocale) {
178                 return sublocale + ".js";
179             }).filter(function(file) {
180                 return !manifest || Loader.indexOf(manifest, file) > -1;
181             });
182             JSUtils.callAll(filesToLoad, ilib.bind(this, function(arr, cb) {
183                 this._loadLocaleFile(arr[0], false, cb);
184             }), function(results) {
185                 callback(results);
186             });
187         }));
188     };
189 
190     return new WebpackLoader(ilib);
191 };
192