Laravel Mix - Is Chaining Required to Ensure Execution Order? - laravel

TLDR;
Do you have to chain Laravel Mix methods to maintain the execution order? Are any methods async that would prevent one from using the following non-chaining pattern, mix.scripts(); mix.js(); mix.sass();?
The few tests I've run suggest I do not need to chain.
An Example
Due to how our Laravel app is setup, we need to have more that one Laravel Mix setup. Instead of copy-n-pasting a webpack.mix.js file and modifying a few lines here and there in each file, we're looking at creating a config object that is passed to a singular webpack.mix.js file. In this file, we would check if various things have been configured, and if so, run the appropriate Mix method. Below is a pseudo-code example.
if ( config.js ) {
mix.js( config.js.src, config.js.dist );
}
if ( config.sass ) {
mix.sass( config.sass.src, config.sass.dist );
}
if ( config.concat ) {
if ( config.concat.styles ) {
// Could be more than one set of files that need to be combined, so array.
config.concat.styles.map( ( files ) => {
mix.styles( files.src, files.dist );
}
}
if ( config.concat.scripts ) {
// Could be more than one set of files that need to be combined, so array.
config.concat.scripts.map( ( files ) => {
mix.scripts( files.src, files.dist );
}
}
}
Currently, our code is more like most examples you see on the web.
mix
.options()
.webpackConfig()
.styles()
.styles()
.scripts()
.js()
.sass();

laravel-mix abstracts configuration of webpack and dynamically generates the webpack config.
The organization of its API implementation is done using Builder pattern with a fluent or chainable interface.
This makes it such that to produce a particular configuration only steps that are necessary to be performed have to be called.
You need to ensure that code in your webpack.mix.js module can be properly imported.
You need to be careful about the ordering of custom tasks such as copy, copyDirectory, combine, version. In v5.0.0, custom tasks are run without any bearing on their asynchronous nature. However there is coming changes to see to it that they are run sequentially.
Other API methods can be called in any order.

The few tests I've run suggest I do not need to chain.
You're absolutly correct!
Laravel Mix is written in JavaScript and makes use of Method Chaining.
You can visualize code execution with OnlinePythonTutor.
If you look at the code below, you'll find that you don't necessarily need to chain methods to maintain execution order.
class Person {
setName(name) {
this.name = name
return this
}
setAge(age) {
this.age = age
return this
}
}
var p = new Person()
p.setName("Alice").setAge(42) // Set name before age
p.setName("Bob") // Set name
p.setAge(42) // Set age
You can visualize this code here

Related

Cypress: using this vs # for fixtures?

So on Cypress's documents it mentions (here: https://docs.cypress.io/guides/core-concepts/variables-and-aliases#Sharing-Context):
Keep in mind that there are use cases for both approaches because they
have different ergonomics.
When using this.users we have access to it synchronously, whereas when
using cy.get('#users') it becomes an asynchronous command.
You can think of the cy.get('#users') as doing the same thing as
cy.wrap(this.users).
This is in regards to using this.fixture vs using cy.get('#fixture') for example (I realize there are other use cases but lets just stick with fixtures for now)
I realize it explains sync vs async above....but when would you WANT to use a synchonous this to access a fixture vs not? Maybe within a then function/callback? (If you needed to access a fixture within a response to a request or something?
One thing it does is reduce callback nesting when multiple aliases are required in one expression, for example
cy.wrap('1').as('a')
// more actions
cy.wrap('2').as('b')
// more actions
cy.wrap('3').as('c')
// assert all values
cy.get('#a').then(a => {
cy.get('#b').then(b => {
cy.get('#c').then(c => {
expect(a+b+c).to.eq(d)
})
})
})
compare to
cy.wrap('1').as('a')
// more actions
cy.wrap('2').as('b')
// more actions
cy.wrap('3').as('c')
// assert all values
cy.then(function() {
expect(this.a + this.b + this.c).to.eq(d)
})

Passing parameters from Command to Converter

I defined a new type of model element as a plug-in; let's refer to it as Foo. A Foo node in the model should translate to a section element in the view. So far, so good. I managed to do that by defining simple conversion rules. I also managed to define a new FooCommand that transforms (renames) selected blocks to Foo.
I got stuck trying to have attributes on those Foo model nodes be translated to attributes on the view elements (and vice-versa). Suppose Foos have an attribute named fooClass which should map to the view element's class attribute.
<Foo fooClass="green-foo"> should map to/from <section class="green-foo">
I can successfully receive parameters in FooCommand, but I can't seem to set them on the blocks being processed by the command:
execute(options = {}) {
const document = this.editor.document;
const fooClass = options.fooClass;
document.enqueueChanges(() => {
const batch = options.batch || document.batch();
const blocks = (options.selection || document.selection).getSelectedBlocks();
for (const block of blocks) {
if (!block.is('foo')) {
batch.rename(block, 'foo');
batch.setAttribute(block, 'fooClass', fooClass);
}
}
});
}
Below is the code for the init function in the Foo plugin, including the model→view and view→model conversions:
init() {
const editor = this.editor;
const doc = editor.document;
const data = editor.data;
const editing = editor.editing;
editor.commands.add('foo', new FooCommand(editor));
doc.schema.registerItem('foo', '$block');
buildModelConverter().for(data.modelToView, editing.modelToView)
.fromElement('foo')
.toElement(modelElement => {
const fooClass = modelElement.item.getAttribute('fooClass'));
return new ContainerElement('section', {'class': fooClass});
});
buildViewConverter().for(data.viewToModel)
.fromElement('section')
.toElement(viewElement => {
let classes = Array.from(viewElement.getClassNames());
let modelElement = new ModelElement('foo', {'fooClass': classes[0]});
return modelElement;
});
}
When I try to run the command via
editor.execute('foo', { fooClass: 'green-foo' })
I can see that the green-foo value is available to FooCommand, but the modelElement in the model→view conversion, on the other hand, has no fooClass attribute.
I'm sure I'm missing the point here and misusing the APIs. I'd be really thankful if someone could shed some light on this issue. I can provide more details, as needed.
Follow-up after initial suggestions
Thanks to #Reinmar and #jodator for their suggestion regarding configuring the document schema to allow for the custom attribute. I really thought that would have taken care of it, but no. It may have been a necessary step anyway, but I'm still unable to get the attribute value from the model element during the model→view conversion.
First, let me add an important piece of information I had left out: the CKEditor5's version I'm working with is 1.0.0-alpha2. I am aware several of the APIs are bound to change, but I would still like to get things working with the present version.
Model→view conversion
If I understand it correctly, one can either pass a string or a function to the toElement call. A question about using the latter: what exactly are the parameters passed to the function? I assumed it would be the model element (node?) to be converted. Is that the case? If so, why is the attribute set on that node via batch.setAttribute (inside a document.enqueueChanges) not available when requested? Should it be?
A sequencing problem?
Additional testing seems to indicate there's some kind of order-of-execution issue happening. I've observed that, even though the attribute is not available when I first try to read it from the modelElement parameter, it will be so if I read it again later. Let me try to illustrate the situation below. First, I'll modify the conversion code to make it use some dummy value in case the attribute value is not available when read:
buildModelConverter().for(data.modelToView, editing.modelToView)
.fromElement('foo')
.toElement(modelElement => {
let fooClass = modelElement.item.getAttribute('fooClass') || 'naught';
let viewElement = new ContainerElement('section');
viewElement.setAttribute('class', fooClass);
return viewElement;
});
Now I reload the page and execute the following instructions on the console:
c = Array.from(editor.document.getRoot().getChildren());
c[1].is('paragraph'); // true
// Changing the node from 'paragraph' to 'foo' and adding an attribute
// 'fooClass' with value 'green-foo' to it.
editor.document.enqueueChanges(() => {
const batch = editor.document.batch();
batch.rename(c[1], 'foo');
batch.setAttribute(c[1], 'fooClass', 'green-foo');
return batch;
});
c[1].is('paragraph'); // false
c[1].is('foo'); // true
c[1].hasAttribute('fooClass'); // true
c[1].getAttribute('fooClass'); // 'green-foo'
Even though it looks like the expected output is being produced, a glance at the generated view element shows the problem:
<section class="naught"/>
Lastly, even if I try to reset the fooClass attribute on the model element, the change is not reflected on the view element. Why is that? Shouldn't changes made via enqueueChanges cause the view to update?
Sorry for the very long post, but I'm trying to convey as many details as I can. Here's hoping someone will spot my mistake or misunderstanding of how the CKEditor 5's API actually works.
View not updating?
I turned to Document's events and experimented with the changesDone event. It successfully addresses the "timing" issue, as it consistently triggers only after all changes have been processed. Still, the problem of the view not updating in response to a change in the model remains. To make it clear, the model does change, but the view does not reflect that. Here is the call:
editor.document.enqueueChanges(() => editor.document.batch().setAttribute(c[1], 'fooClass', 'red-foo'));
To be 100% sure I wrote the whole feature myself. I use the 1.0.0-beta.1 API which is completely different than what you had.
Basically – it works. It isn't 100% correct yet, but I'll get to that.
How to convert an element+attribute pair?
The thing when implementing a feature which needs to convert element + attribute is that it requires handling the element and attribute conversion separately as they are treated separately by CKEditor 5.
Therefore, in the code below you'll find that I used elementToElement():
editor.conversion.elementToElement( {
model: 'foo',
view: 'section'
} );
So a converter between model's <foo> element and view's <section> element. This is a two-way converter so it handles upcasting (view -> model) and downcasting (model -> view) conversion.
NOTE: It doesn't handle the attribute.
Theoretically, as the view property you could write a callback which would read the model element's attribute and create view element with this attribute set too. But that wouldn't work because such a configuration would only make sense in case of downcasting (model -> view). How could we use that callback to downcast a view structure?
NOTE: You can write converters for downcast and upcast pipelines separately (by using editor.conversion.for()), in which case you could really use callbacks. But it doesn't really make sense in this case.
The attribute may change independently!
The other problem is that let's say you wrote an element converter which sets the attribute at the same time. Tada, you load <section class=ohmy> and gets <foo class=ohmy> in your model.
But then... what if the attribute will change in the model?
In the downcast pipeline CKEditor 5 treats element changes separately from attribute changes. It fires them as separate events. So, when your FooCommand is executed on a heading it calls writer.rename() and we get the following events in DowncastDispatcher:
remove with <heading>
insert:section
But then the attribute is changed too (writer.setAttribute()), so we also get:
setAttibute:class:section
The elementToElement() conversion helper listens to insert:section event. So it's blind to setAttribute:class:selection.
Therefore, when you change the value of the attribute, you need the attributeToAttribute() conversion.
Sequencing
I didn't want to reply to your question before we released 1.0.0-beta.1 because 1.0.0-beta.1 brought the Differ.
Before 1.0.0-beta.1 all changes were converted immediately when they were applied. So, rename() would cause immediate remove and insert:section events. At this point, the element that you got in the latter one wouldn't have the class attribute set yet.
Thanks to the Differ we're able to start the conversion once all the changes are applied (after change() block is executed). This means that the insert:section event is fired once the model <foo> element has the class attribute set already. That's why you could write a callback-based converters... bur you shouldn't :D
The code
import { downcastAttributeToAttribute } from '#ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
import { upcastAttributeToAttribute } from '#ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
class FooCommand extends Command {
execute( options = {} ) {
const model = this.editor.model;
const fooClass = options.class;
model.change( writer => {
const blocks = model.document.selection.getSelectedBlocks();
for ( const block of blocks ) {
if ( !block.is( 'foo' ) ) {
writer.rename( block, 'foo' );
writer.setAttribute( 'class', fooClass, block );
}
}
} );
}
}
class FooPlugin extends Plugin {
init() {
const editor = this.editor;
editor.commands.add( 'foo', new FooCommand( editor ) );
editor.model.schema.register( 'foo', {
allowAttributes: 'class',
inheritAllFrom: '$block'
} );
editor.conversion.elementToElement( {
model: 'foo',
view: 'section'
} );
editor.conversion.for( 'upcast' ).add(
upcastAttributeToAttribute( {
model: 'class',
view: 'class'
} )
);
editor.conversion.for( 'downcast' ).add(
downcastAttributeToAttribute( {
model: 'class',
view: 'class'
} )
);
// This should work but it does not due to https://github.com/ckeditor/ckeditor5-engine/issues/1379 :(((
// EDIT: The above issue is fixed and will be released in 1.0.0-beta.2.
// editor.conversion.attributeToAttribute( {
// model: {
// name: 'foo',
// key: 'class'
// },
// view: {
// name: 'section',
// key: 'class'
// }
// } );
}
}
This code works quite well, except the fact that it converts the class attribute on any possible element that has it. That's because I had to use very generic downcastAttributeToAttribute() and upcastAttributeToAttribute() converters because of a bug that I found (EDIT: it's fixed and will be available in 1.0.0-beta.2). The commented out piece of code is how you it should be defined if everything worked fine and it will work in 1.0.0-beta.2.
It's sad that we missed such a simple case, but that's mainly due to the fact that all our features... are much more complicated than this.

how to ignore ordering of reselect selectors when composing selectors

As I compose more selectors together, I'm finding that I'm reordering where the selectors are defined. For example,
export const selectNav = state => state.nav;
export const selectPage = state => state.page;
export const selectNavAndPage = createSelector([
selectNav,
selectPage,
], (nav, page) => {
});
export const selectFoo = state => state.foo;
export const selectNavAndPageAndFoo = createSelector([
selectNavAndPage,
selectFoo,
], (navAndPage, foo) => {
});
This is a simple example, but I could not define selectNavAndPage below selectNavAndPageAndFoo. As more selectors get composed and selectors of selectors get composed, then I need to make sure all the sub-selectors are defined at the top before I use them.
Is there some way to create these selectors such that ordering doesn't matter?
I was worried about the same problem and I created this npm module define-selectors. This is a module that delays the definition of the selector to solve the ordering of selector definition problem and adds other features to it. It has not been stable yet, but I will use it on my project to make it stable and be improved.
For more information please go to github page and read the README and source files.
I'm pretty sure this is just related to how the ES6 const keyword works. With const, variables do not exist until that line, so if you want to reference a const variable, you need to write that code after the variable declaration. With var, all variables are hosted to the top of the scope.
So, either use var so that you can reference things out of order, or continue using const and define each function in the correct order for usage and references.
If you don't mind a little extra typing, here is another approach which requires defining a 'cms' utility function that wraps the createSelector function and takes advantage of function hoisting:
import {createSelector} from 'reselect';
// create memoized selector
function cms(ctx, ...args) {
if (!ctx.selector) ctx.selector = createSelector(...args);
return ctx.selector;
}
// define the selectors out of order...
export function getBaz(state) {
return cms(
getBaz // the function itself as context
, getBar
, bar => bar.baz
)(state);
}
export function getBar(state) {
return cms(
getBar
, getFoo
, foo => foo.bar
)(state);
}
export function getFoo(state) {
return state.foo;
}
This is not as elegant as simply defining the selectors in order, but maybe someone else can take this idea and improve on it.

Skip single file from Minifying?

I'm trying to use ASP.Nets BundleTable to optomize some javascript files, but have run into a problem where a specific addon (jQuery-Timepicker) fails to work when the code has been minified. See here.
Bundle code is currently similar to:
// Add our commonBundle
var commonBundle= new Bundle("~/CommonJS" + culture.ToString());
// JQuery and related entries.
commonBundle.Include("~/Scripts/jquery-1.7.2.js");
commonBundle.Include("~/Scripts/jquery-ui-1.8.22.js");
commonBundle.Include("~/Scripts/jquery.cookie.js");
commonBundle.Include("~/Scripts/jquery-ui/jquery-ui-timepicker-addon.js"); // This is the one that does not work when bundled
// JS Transformer
commonBundle.Transforms.Add(new JsMinify());
BundleTable.Bundles.Add(commonBundle);
If I remove the jquery-ui-timepicker-addon.js file, then include it separate in my webpage, then it works properly. (Otherwise I get the Uncaught TypeError: undefined is not a function error).
I'm wondering if I can somehow setup my bundling code to skip minifying this one file (but still have it included in the bundle)? I've been looking around but have not come up with any solutions for doing so.
So the issue is that all the files are bundled together, and then the entire bundle is minimized. As a result you aren't going to easily be able to skip minification of just one file. Probably the best way to do this would be to create a new Transform that appended the contents of this file you want unminified. Then you would append this Transform to your registered ScriptBundle:
commonBundle.Transforms.Add(new AppendFileTransform(""~/Scripts/jquery-ui/jquery-ui-timepicker-addon.js""));
AppendFileTransform would simply append the contents of the file to the bundled response. You would no longer include the timepicker in the bundle explicitly, but instead this transform would be including it, and this would effectively give you the behavior you are looking since the JsMinify transform would run first and minify the bundle, and then you would add the file you want at the end unminified.
This can be solved better from the other direction - instead of trying to not minify a single file, add transforms for individual items instead.
First - create a class that implements IItemTransform and uses the same code to minify the given input:
public class JsItemMinify : System.Web.Optimization.IItemTransform
{
public string Process(string includedVirtualPath, string input)
{
var min = new Microsoft.Ajax.Utilities.Minifier();
var result = min.MinifyJavaScript(input);
if (min.ErrorList.Count > 0)
return "/*minification failed*/" + input;
return result;
}
}
Second - add this item transform to the individual files and remove the bundle transform:
var commonBundle= new Bundle("~/CommonJS");
// the first two includes will be minified
commonBundle.Include("~/Scripts/jquery-1.7.2.js", new JsItemMinify());
commonBundle.Include("~/Scripts/jquery-ui-1.8.22.js", new JsItemMinify());
// this one will not
commonBundle.Include("~/Scripts/jquery.cookie.js");
// Remove the default JsMinify bundle transform
commonBundle.Transforms.Clear();
BundleTable.Bundles.Add(commonBundle);
You cannot setup Bundle to skip minifying certain files and to minify rest of the files.
You could implement your own Bundle or Transform by overriding Bundle.ApplyTransform or JsMinify.Process methods, but you would need to take care not to break change-tracking of files, key generation, cache invalidation, etc... (or doing some ugly hack). It's not worth the effort.
I would keep separate js file, as you already mentioned.
This is just complete example based on Hao Kung's answer
var myBundle = new ScriptBundle("~/bundles/myBundle").Include(
"~/Scripts/script1.js",
"~/Scripts/script2.js",
);
myBundle.Transforms.Add(new AppendFileTransform("~/Scripts/excludedFile.min.js"));
bundles.Add(myBundle);
And here is example implementation of the AppendFileTransform:
public class AppendFileTransform : IBundleTransform
{
private readonly string _filePath;
public AppendFileTransform(string filePath)
{
_filePath = filePath;
}
public void Process(BundleContext context, BundleResponse response)
{
response.Content += File.ReadAllText(context.HttpContext.Server.MapPath(_filePath));
}
}

testng specify different Users

I am running our automated tests using TestNG. The reason we picked TestNG is because we can send variables inputs into the test methods example public void testXX( String userId ) and the userId can change for each test.
The code below shows three different userIds I can use to execute my tests. So my exact same test will run three times for each of the three different users. This feature is awesome and really enables me to have multiple tests under different scenarios because each of our users carry different profiles.
// All valid Pricing Leads
#DataProvider(name = "userIds")
public Object[][] createPricingLeadUsersParameters() {
return new Object[][] {
{ "TestUser001" },
{ "TestUser002" },
{ "TestUser003" }
};
}
#Test( dataProvider = "userIds" )
public void createGroup( String userIds) {
............
}
The problem I am having right now is during certain conditions I can only have one userId used or else all of my tests will fail. I would like to keep my exact same test but only pass in on userId not the three shown above. It there a way to configure TestNG to make this variable on the command line so at times I would use the three defined, but under another condition it would only be one of the three or a new userId?
Sure, there are plenty of ways to do this. How about passing a system property when you run TestNG?
java -Dfoo=bar org.testng.TestNG...
and then your data provider can test the value of foo with System.getProperty() and adjust what it returns accordingly.

Resources