In-repo addon writing public files on build causes endless build loop on serve - ember.js

I'm having difficulty with my in-repo addon writing to appDir/public. What I'd like to do is write out a JSON file on each build to be included in the app /dist. The problem I'm running into is when running "ember serve", the file watcher detects the new file and rebuilds again, causing an endless loop.
I've tried writing the JSON file using preBuild() and postBuild() hooks, saving to /public, but after build, the watcher detects it and rebuild over and over, writing a new file again each time. I also tried using my-addon/public folder and writing to that, same thing.
The only thing that partially works is writing on init(), which is fine, except I don't see the changes using ember serve.
I did try using the treeForPublic() method, but did not get any further. I can write the file and use treeForPublic(). This only runs once though, on initial build. It partially solves my problem, because I get the files into app dist folder. But I don't think ember serve will re-run treeForPublic on subsequent file change in the app.
Is there a way to ignore specific files from file watch? Yet still allow files to include into the build? Maybe there's an exclude watch property in ember-cli-build?
Here's my treeForPublic() , but I'm guessing my problems aren't here:
treeForPublic: function() {
const publicTree = this._super.treeForPublic.apply(this, arguments);
const trees = [];
if (publicTree) {
trees.push(publicTree);
}
// this writes out the json
this.saveSettingsFile(this.pubSettingsFile, this.settings);
trees.push(new Funnel(this.addonPubDataPath, {
include: [this.pubSettingsFileName],
destDir: '/data'
}));
return mergeTrees(trees);
},
UPDATE 05/20/2019
I should probably make a new question at this point...
My goal here is to create an auto-increment build number that updates both on ember build and ember serve. My comments under #real_ates's answer below help explain why. In the end, if I can only use this on build, that's totally ok.
The answer from #real_ate was very helpful and solved the endless loop problem, but it doesn't run on ember serve. Maybe this just can't be done, but I'd really like to know either way. I'm currently trying to change environment variables instead of using treeforPublic(). I've asked that as a separate question about addon config() updates to Ember environment:
Updating Ember.js environment variables do not take effect using in-repo addon config() method on ember serve
I don't know if can mark #real_ate's answer as the accepted solution because it doesn't work on ember serve. It was extremely helpful and educational!

This is a great question, and it's often something that people can be a bit confused about when working with broccoli (I know for sure that I've been stung by this in the past)
The issue that you have is that your treeForPublic() is actually writing a file to the source directory and then you're using broccoli-funnel to select that new custom file and include it in the build. The correct method to do this is instead to use broccoli-file-creator to create an output tree that includes your new file. I'll go into more detail with an example below:
treeForPublic: function() {
const publicTree = this._super.treeForPublic.apply(this, arguments);
const trees = [];
if (publicTree) {
trees.push(publicTree);
}
let data = getSettingsData(this.settings);
trees.push(writeFile('/data/the-settings-file.json', JSON.stringify(data)));
return mergeTrees(trees);
}
As you will see the most of the code is exactly the same as your example. The two main differences are that instead of having a function this.saveSettingsFile() that writes out a settings file on disk we now have a function this.getSettingsData() that returns the content that we would like to see in the newly created file. Here is the simple example that we came up with when we were testing this out:
function getSettingsData() {
return {
setting1: 'face',
setting2: 'my',
}
}
you can edit this function to take whatever parameters you need it to and have whatever functionality you would like.
The next major difference is that we are using the writeFile() function which is actually just the broccoli-file-creator plugin. Here is the import that you would put at the top of the file:
let writeFile = require('broccoli-file-creator');
Now when you run your application it won't be writing to the source directory any more which means it will stop constantly reloading 🎉
This question was answered as part of "May I Ask a Question" Season 2 Episode 2. If you would like to see us discuss this answer in full you can check out the video here: https://youtu.be/9kMGMK9Ur4E

Related

Get ScriptOrigin from v8::Module

It seems trivial, but I've searched far and wide.
I'm using this resource to make v8 run with ES Modules and I'm trying to implement my own search/load algorithm. Thus far, I've managed to make a simple system which loads a file from a known location, however I'd like to implement external modules. This means that the known location is actually unknown throughout the application. Take the following directory tree as an example:
~/
- index.js
import 'module1_index'; // This is successfully resolved to /libs/module1/module1_index.js
/libs/module1/
- module1_index.js
export * from './lib.js' // This import fails because it is looking for ./lib.js in ~/source
- lib.js
export /* literally anything */
The above example begins by executing the index.js file from ~. When module1_index.js is executed, lib.js is looked for from ~ and consequently fails. In order to address this, the files must be looked for relative to the file being executed at the moment, however I have not found a means to do this.
First Attempt
I'm given the opportunity to look for the file in the callResolve method (main.cpp:280):
v8::MaybeLocal<v8::Module> callResolve(v8::Local<v8::Context> context, v8::Local<v8::String> specifier, v8::Local<v8::Module> referrer)
or in loadModule (main.cpp:197)
v8::MaybeLocal<v8::Module> loadModule(char code[], char name[], v8::Local<v8::Context> cx)
however, as mentioned, I have found no function by which to extract the ScriptOrigin from the module. I should mention, when files are successfully resolved, the ScriptOrigin is initiated with the exact path to the file, and is reliable.
Second Attempt
I set up a stack, which keeps track of the current file being executed. Every import which is made is pushed onto the stack. Once the file has finished executing, it is popped. This also did not work, as there was no way to reliably determine once the file had finished executing.
It seems that the loadModule function does just that: loads. It does not execute, so I cannot pop after the module has loaded, as the imports are not fully resolved. The checkModule/execModule functions are only invoked on dynamic imports, making them useless to determining the completion of a static import.
I'm at a loss. I'm not familiar with v8 enough to know where to look, although I have dug through some NodeJS source code looking for an implementation, to no avail.
Any pointers are greatly appreciated.
Thanks.
Jake.
I don't know much about module resolution, but looking at V8's sources, I can see an example mapping a v8::Module to a std::string absolute_path, which sounds like what you're looking for. I'm not copying the whole code here, because the way it uses custom metadata is a bit involved; the short story is that it keeps a std::unordered_map to keep data about each module's source on the side. (I wonder if it would be possible to use Module::ScriptId() as that map's key, for simplification.)
Code search finds a bunch more example uses of InstantiateModule, mostly in tests. Tests often serve as useful examples/documentation :-)

GLB/GLTF File Loading with Storybook and Webpack with file-loader

I have a component library I am creating with Storybook that needs access to .glb/.gltf files. Based on research, it seemed like the best thing to do here was to use the file-loader Webpack functionality, and augment the storybook main.js as such:
// .storybook/main.js
module.exports = {
"stories": [
"../src/**/*.stories.mdx",
"../src/**/*.stories.#(js|jsx|ts|tsx)"
],
"addons": [
"#storybook/addon-links",
"#storybook/addon-essentials",
"#storybook/preset-create-react-app"
],
webpackFinal: async (config, { configType }) => {
config.module.rules.push({
test: /\.(glb|gltf)$/,
use: ['file-loader'],
include: path.resolve(__dirname, '../'),
});
return config;
},
};
Then, in my jsx file that references the mesh:
// src/components/MeshLoader.jsx
import MyMeshFile from "./meshes/MyMesh.glb";
import { useGLTF } from "#react-three/drei";
export default function Model(props) {
const group = useRef();
const { nodes, materials } = useGLTF(MyMeshFile);
// Do more stuff with these things
}
When I run compile, everything works, and if I log what MyMeshFile is, I get a path like:
static/media/MyMesh.976a5ad2.glb, as expected.
However, the rest breaks with an error Uncaught Unexpected token e in JSON at position 0, basically on account of the useGLTF function failing at the contents of that file.
It turns out that http://localhost:6006/static/media/MyMesh.976a5ad2.glb is actually a file with the contents of
export default __webpack_public_path__ + "178cb3da7737741d81a5d4f0c2bcc161.glb";
So it seems like there is some redirection happening. If I direct the browser at http://localhost:6006/178cb3da7737741d81a5d4f0c2bcc161.glb, I get the file I want.
My first question, is whether this is the expected behavior here, given the way I have things set up. If so, it seems like I would have to parse the contents of the file path given by Webpack, and use that to get the actual path. That seems to be a bit convoluted, so is there a better way of handling this?
Thanks for the help!
UPDATE:
I have tested with the gltf-webpack-loader loader, by adding the following to the .storybook/main.js file:
...
config.module.rules.push({
test: /\.(gltf)$/, // Removed gltf from file-loader
use: [{loader: "gltf-webpack-loader"}]
})
...
And tried the same thing with a gltf file. I get the same behavior of receiving the "redirect" file instead of the actual one I want.
So it turns out that there is currently a bug with "#storybook/preset-create-react-app" that is causing this issue. Removing that add-on seems to resolve the issue described here, although it does produce a warning that:
Storybook support for Create React App is now a separate preset.
WARN To use the new preset, install `#storybook/preset-create-react-app` and add it to the list of `addons` in your `.storybook/main.js` config file.
WARN The built-in preset has been disabled in Storybook 6.0.

How to use Polymer 2 Build Process?

I know this might be a duplicate of this question but there was no clear answer ever.
Polymer 2 Framework has a quite good documentation so far but when it comes to the build process there`s not enough explanation.
I've successfully created my own element, also with external scripts referenced and everything runs fine with
polymer serve --open
But I've spent so much time to get this built and to include the output into a minimal HTML template without success. Is it really that tricky?
I`ve even tried again with the empty element template, no chance. This template has a nearly blank polymer.json:
{
"lint": {
"rules": [
"polymer-2"
]
}
}
If I build that right away with
polymer build
(should use default build behaviour) then I get a default build folder with an index.html and bower_components folder. There`s no reference to my created custom element ("Hello ...").
What do I have to do to get a final build of this "Hello Something" template and include it into a minimum html page?
There are a lot of different ways to go from single component to app with that single component, and I'm not going to say 100% that this is the best, but it should work. "What do I have to do to get a final build of this "Hello Something" template and include it into a minimum html page?", is a pretty flexible request, so even in the below there could be lots of alternatives for you, but the following would be my suggestion. We'll start in the command line, from the Desktop or a folder where you keep you projects.
mkdir minimal-html-page
cd minimal-html-page
polymer init
// here make sure you choose `polymer-2-application` other questions re of little consequence in this specific use case
bower install {yourComponent} --save
// in that case that you haven't published to github, copy and paste also works. Paste into the `src` directory if that's the case.
atom .
// or whatever editor you prefer
Now you're in your project, and there is just one change needed to get you going.
index.html
Change the current HTML import
<link rel="import" href="/src/build-test-app/build-test-app.html">
to link to your component.
At this point you should be free to use polymer build to have the project built, then it'll structure your code to be deployed in other applications via something that will look like
<script src="/bower_components/webcomponentsjs/webcomponents-loader.js"></script>
<link rel="import" href="/src/build-test-app/build-test-app.html">
<build-test-app></build-test-app>
This is the most basic of builds and won't account for any x-browser or performance goals you might have in your project. You'll also see in the build/default directory all the files you'd need to copy/paste to use the component elsewhere.
If you do have x-browser expectations, you can solve them fairly easily by using the polymer build --compile command. This will create slightly more complex embed code to manage either side of the ES5/6 capability boundary and will look something like:
<script>!function(e){var r=e.babelHelpers={};r.typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},r.classCallCheck=function(e,r){if(!(e instanceof r))throw new TypeError("Cannot call a class as a function")},r.createClass=function(){function e(e,r){for(var t=0;t<r.length;t++){var n=r[t];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(r,t,n){return t&&e(r.prototype,t),n&&e(r,n),r}}(),r.defineEnumerableProperties=function(e,r){for(var t in r){var n=r[t];n.configurable=n.enumerable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,t,n)}return e},r.defaults=function(e,r){for(var t=Object.getOwnPropertyNames(r),n=0;n<t.length;n++){var o=t[n],i=Object.getOwnPropertyDescriptor(r,o);i&&i.configurable&&void 0===e[o]&&Object.defineProperty(e,o,i)}return e},r.defineProperty=function(e,r,t){return r in e?Object.defineProperty(e,r,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[r]=t,e},r.extends=Object.assign||function(e){for(var r=1;r<arguments.length;r++){var t=arguments[r];for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])}return e},r.get=function e(r,t,n){null===r&&(r=Function.prototype);var o=Object.getOwnPropertyDescriptor(r,t);if(void 0===o){var i=Object.getPrototypeOf(r);return null===i?void 0:e(i,t,n)}if("value"in o)return o.value;var a=o.get;if(void 0!==a)return a.call(n)},r.inherits=function(e,r){if("function"!=typeof r&&null!==r)throw new TypeError("Super expression must either be null or a function, not "+typeof r);e.prototype=Object.create(r&&r.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),r&&(Object.setPrototypeOf?Object.setPrototypeOf(e,r):e.__proto__=r)},r.instanceof=function(e,r){return null!=r&&"undefined"!=typeof Symbol&&r[Symbol.hasInstance]?r[Symbol.hasInstance](e):e instanceof r},r.newArrowCheck=function(e,r){if(e!==r)throw new TypeError("Cannot instantiate an arrow function")},r.objectDestructuringEmpty=function(e){if(null==e)throw new TypeError("Cannot destructure undefined")},r.objectWithoutProperties=function(e,r){var t={};for(var n in e)r.indexOf(n)>=0||Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t},r.possibleConstructorReturn=function(e,r){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!r||"object"!=typeof r&&"function"!=typeof r?e:r},r.set=function e(r,t,n,o){var i=Object.getOwnPropertyDescriptor(r,t);if(void 0===i){var a=Object.getPrototypeOf(r);null!==a&&e(a,t,n,o)}else if("value"in i&&i.writable)i.value=n;else{var u=i.set;void 0!==u&&u.call(o,n)}return n},r.slicedToArray=function(){function e(e,r){var t=[],n=!0,o=!1,i=void 0;try{for(var a,u=e[Symbol.iterator]();!(n=(a=u.next()).done)&&(t.push(a.value),!r||t.length!==r);n=!0);}catch(e){o=!0,i=e}finally{try{!n&&u.return&&u.return()}finally{if(o)throw i}}return t}return function(r,t){if(Array.isArray(r))return r;if(Symbol.iterator in Object(r))return e(r,t);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),r.taggedTemplateLiteral=function(e,r){return Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(r)}}))},r.temporalRef=function(e,r,t){if(e===t)throw new ReferenceError(r+" is not defined - temporal dead zone");return e},r.temporalUndefined={},r.toArray=function(e){return Array.isArray(e)?e:Array.from(e)},r.toConsumableArray=function(e){if(Array.isArray(e)){for(var r=0,t=Array(e.length);r<e.length;r++)t[r]=e[r];return t}return Array.from(e)}}("undefined"==typeof global?self:global);</script>
<script>if (!window.customElements) { document.write('<!--'); }</script>
<script type="text/javascript" src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
<!--! do not remove -->
<script src="/bower_components/webcomponentsjs/webcomponents-loader.js"></script>
<link rel="import" href="/src/build-test-app/build-test-app.html">
<build-test-app></build-test-app>
The <!--! do not remove --> part is super not kidding and it integral to making sure the compiled code works in ES6 environments.
Beyond that, there is a whole world of things that you can do to tune this up for production deployment. I'd research https://www.polymer-project.org/2.0/docs/tools/polymer-cli-commands#build and https://www.polymer-project.org/2.0/docs/tools/polymer-json to get a better handle on that.
Re polymer build "This command is for app projects only." (https://www.polymer-project.org/2.0/docs/tools/polymer-cli-commands#build)
Also, shell is required. See https://www.polymer-project.org/2.0/docs/tools/polymer-json#shell.

Including specific style sheets or javascript in ember-cli-build

The problem
I am working on an Ember.js project which has different versions (products) for different clients. Though the functionality is more or less the same, the styling of each product differs big time. Hence we have "default" and product specific style sheets. I have been asked to modify the build code so that only the corresponding .css (.less) files are compiled into the final app.
Originally I was looking at this issue from the wrong side: I tried to exclude the folders containing the unnecessary files with little success. Only then did I realize that it makes more sense not to include the product specific files by default and add them to the tree during the build.
The solution
After changing my point of view I found out there is another way around. I changed the style sheets so that all the "default looks" went into an import-base.less and I created an import-[name_of_product].less for each of the products, with the latters containing the import statement to the default looks, so I only have one file to build. Using the outputPaths option in EmberApp and assuming that the name of the product is stored in the process environmental variable called FLAVOUR my code looks as follows.
// ember-cli-build.js
/* global require, module */
var EmberApp = require('ember-cli/lib/broccoli/ember-app');
module.exports = function(defaults) {
// y u do dis
const options = { outputPaths: { app: { css: { app: false } } } };
const lessFileName = 'import-' + process.env.FLAVOUR.toLowerCase();
options.outputPaths.app.css[lessFileName] = 'assets/application.css'
const app = new EmberApp(defaults, options);
return app.toTree();
};
There is always something
The only problem with that code is that it still needs an app.less and that line of code or else the build fails, couldn't (haven't had time to) figure out a solution.
I also have to mention that the above solution doesn't resolve the original problem, which was:
How to exclude specific files from the working directory before using app.toTree() so that they wouldn't increase file size unnecessarily. Lux was so kind and pointed out that probably in-repo-addons are to be used for such purposes. Yet again, haven't had time to check. :(
I think you can just use funnel!
something like this:
return new Funnel(app.toTree(), {
include: ['**/*']
exclude: ['styles/*.css']
});
general you can do anything you can do in a Brocfile in your ember-cli-build.js!

Joomla: Plugins for a plugin - how to 'advertise' capabilities?

I am working on a plugin which will have its own plugins to handle various events.
Now I'm thinking of enabling this plugins to add their own "commands". But I wonder how to treat that most efficiently. I have a list of my own commands which I search in the article anyway. Should I then just trigger a DoWhatYouWant($article)-event - or, since I do the searching (and parsing of params) anyway, perhaps I could build a global command-list and then trigger an "ExecuteCommand($article,$cmd,$params)"-event? Sounds nicer, but then (I think) I'd have to build this command-list (so that my program know what to look for), so every plugin would have to somehow 'advertise' what it could do, i.e. the names of commands it could handle - and I have no idea how that could be done.
Or is there a better (more standardized?) approach?
If you import your plugins trough the plugin helper
JPluginHelper::importPlugin('mycmdplugins');
then you can get all available commands which are supported by your sub plugins like
$cmds = JDispatcher::getInstance()->trigger('onMyAwesomeCmds');
With the $cmds variable you know now which commands are supported by the sub plugins and you can parse the article for them. Then you can do
foreach ($cmds as $cmd) {
preg_match_all("{".$cmd."*}", $article->text, $matches, PREG_SET_ORDER);
if (!empty($matches)) {
JDispatcher::getInstance()->trigger('onMyAwesome'.ucfirst($cmd), array($article, $params));
}
}
To eliminate more repeating tasks I suggest that the additional plugins will extend a base class from your plugins folder.