With ASP.NET 5, I am moving from ASP.NET MVC's bundling system to a Bower/Grunt workflow for my client-side package management and bundling/minification. I'm trying to figure out how to closely replicate the MVC bundling functionality.
With MVC bundling, I manually created all of my bundles and then call out to a helper method such as: #Styles.Render("~/bundles/styles/site"). In development I get separate link element for each CSS file in the bundle and in production I get a single combined and minified CSS file.
I have successfully set up Grunt with Bower to pull down packages and copy them into the appropriate final destination, but there's no differentiation between development and production. What's the closest functionality to my existing MVC bundling workflow?
This article below explain a very nice way to have both (Bower and .NET Bundle Config) playing nicely together...
http://robertnoack.com/x86/2014/07/asp-net-mvc-using-bowergruntwiredep-to-inject-javascript-dependencies-into-bundleconfig-cs/
The key info, is to use a Grunt Task (wiredep) to target the BundleConfig.cs file so you could still use bower to manage your dependencies and still use BundleConfig to let .NET minify your stuff for you.
After a day of pain, I have got grunt basically behaving in the same manner as asp.net minification with debug & release builds.
I have put together a git repo so you can just pull all the relevant files and mod as required.
https://github.com/glaidler/grunt-equivalent-asp.net-minification
You can use the grunt contrib css min task to create a bundle of your css
Read this post : http://love2dev.com/#!article/Using-GruntJS-to-Bundle-and-Minify-JavaScript-and-CSS
I'm a pretty big fan of the way Yeoman handles assets in the angular generator. It uses wiredep to automatically include Bower packages in your index.html. Usemin is used to group files you want in bundles and Filerev updates the asset locations and adds a cache breaker. Here is the a sample of some of the Grunt settings I have.
wiredep: {
app: {
src: ['<%= yeoman.app %>/index.html'],
exclude: ['bootstrap.css'],
fileTypes: {
html: {
replace: {
js: '<script src="/{{filePath}}"></script>',
css: '<link rel="stylesheet" href="/{{filePath}}" />'
}
}
},
ignorePath: /\.\.\//
}
},
// Renames files for browser caching purposes
filerev: {
dist: {
src: [
'<%= yeoman.dist %>/scripts/{,*/}*.js',
'<%= yeoman.dist %>/styles/{,*/}*.css',
'<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
'<%= yeoman.dist %>/styles/fonts/*'
]
}
},
// Reads HTML for usemin blocks to enable smart builds that automatically
// concat, minify and revision files. Creates configurations in memory so
// additional tasks can operate on them
useminPrepare: {
html: '<%= yeoman.app %>/index.html',
options: {
dest: '<%= yeoman.dist %>',
flow: {
html: {
steps: {
js: ['concat', 'uglifyjs'],
css: ['cssmin']
},
post: {}
}
}
}
},
// Performs rewrites based on filerev and the useminPrepare configuration
usemin: {
html: ['<%= yeoman.dist %>/**/*.html'],
css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
options: {
assetsDirs: ['<%= yeoman.dist %>', '<%= yeoman.dist %>/images'],
patterns: {
js: [
[/templateUrl:"(templates[/A-Za-z0-9]*\.html)"/]
]
}
}
},
The relevant npm packages are grunt-wiredep, grunt-filerev, and grunt-usemin
You will need to add a grunt build process after MSBuild that takes the files in your bin folder and runs these grunt tasks on them.
Related
I've scoured the internet and have bits and pieces but nothing is coming together for me. I have a local Drupal environment running with Lando. I've successfully installed and configured webpack. Everything is working except when I try to watch or hot reload.
When I run lando npm run build-dev (that currently uses webpack --watch I can see my changes compiled successfully into the correct folder. However, when I refresh my Drupal site, I do not see that changes. The only time I see my updated JS changes are when I run lando drush cr to clear cache. Same things are happening when I try to configure the webpack-dev-server. I can get everything to watch for changes and compile correctly but I cannot get my browser to reload my files, they stay cached. I'm at a loss.
I've tried configuring a proxy in my .lando.yml , and have tried different things with the config options for devServer. I'm just not getting a concise answer, and I just don't have the knowledge to understand exactly what is happening. I believe it has to do with Docker containers not being exposed to webpack (??) but I don't understand how to configure this properly.
These are the scripts I have set up in my package.json , build outputs my production ready files into i_screamz/js/dist, build-dev starts a watch and compiles non-minified versions to i_screamz/js/dist-dev - start I have in here from trying to get the devServer to work. I'd like to get webpack-dev-server running as I'd love to have reloading working.
"scripts": {
"start": "npm run build:dev",
"build:dev": "webpack --watch --progress --config webpack.config.js",
"build": "NODE_ENV=production webpack --progress --config webpack.config.js"
},
This is my webpack.config.js - no sass yet, this is just a working modular js build at this point.
const path = require("path");
const BrowserSyncPlugin = require('browser-sync-webpack-plugin');
const isDevMode = process.env.NODE_ENV !== 'production';
module.exports = {
mode: isDevMode ? 'development' : 'production',
devtool: isDevMode ? 'source-map' : false,
entry: {
main: ['./src/index.js']
},
output: {
filename: isDevMode ? 'main-dev.js' : 'main.js',
path: isDevMode ? path.resolve(__dirname, 'js/dist-dev') : path.resolve(__dirname, 'js/dist'),
publicPath: '/web/themes/custom/[MYSITE]/js/dist-dev'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
]
},
plugins: [
new BrowserSyncPlugin({
proxy: {
target: 'http://[MYSITE].lndo.site/',
proxyReq: [
function(proxyReq) {
proxyReq.setHeader('Cache-Control', 'no-cache, no-store');
}
]
},
open: false,
https: false,
files: [
{
match: ['**/*.css', '**/*.js'],
fn: (event, file) => {
if (event == 'change') {
const bs = require("browser-sync").get("bs-webpack-plugin");
if (file.split('.').pop()=='js') {
bs.reload();
} else {
bs.stream();
}
}
}
}
]
}, {
// prevent BrowserSync from reloading the page
// and let Webpack Dev Server take care of this
reload: false,
injectCss: true,
name: 'bs-webpack-plugin'
}),
],
watchOptions: {
aggregateTimeout: 300,
ignored: ['**/*.woff', '**/*.json', '**/*.woff2', '**/*.jpg', '**/*.png', '**/*.svg', 'node_modules'],
}
};
And here is the config I have setup in my .lando.yml - I did have the proxy key in here but it's been removed as I couldn't get it setup right.
name: [MYSITE]
recipe: pantheon
config:
framework: drupal8
site: [MYPANTHEONSITE]
services:
node:
type: node
build:
- npm install
tooling:
drush:
service: appserver
env:
DRUSH_OPTIONS_URI: "http://[MYSITE].lndo.site"
npm:
service: node
settings.local.php
<?php
/**
* Disable CSS and JS aggregation.
*/
$config['system.performance']['css']['preprocess'] = FALSE;
$config['system.performance']['js']['preprocess'] = FALSE;
I've updated my code files above to reflect reflect a final working setup with webpack. The main answer was a setting in
/web/sites/default/settings.local.php
**Disable CSS & JS aggregation. **
$config['system.performance']['css']['preprocess'] = FALSE;
$config['system.performance']['js']['preprocess'] = FALSE;
I found a working setup from saschaeggi and just tinkered around until I found this setting. So thank you! I also found more about what this means here. This issue took me way longer than I want to admit and it was so simple. I don't know why the 'Disabling Caching css/js aggregation' page never came up when I was furiously googling a caching issue. Hopefully this answer helps anyone else in this very edge case predicament.
I have webpack setup within my theme root folder with my Drupal theme files. I run everything with Lando, including NPM. I found a nifty trick to switch the dist-dev and dist libraries for development / production builds from thinkshout.
I should note my setup does not include hot-reloading but I can at least compile my files and refresh immediately and see my changes. The issue I was having before is that I would have to stop my watches to drush cr and that workflow was ridiculous. I've never gotten hot reloading to work with with either BrowserSync or Webpack Dev Server and I might try to again but I need to move on with my life at this point.
I've also note included sass yet, so these files paths will change to include compilation and output for both .scss and .js files but this is the basic bare min setup working.
I wanted to tackle file cache updating in a Svelte app and wanted this part of the rollup build. I decide to add a querystring parameter to the file references (such as 'index.html?v=0.1') in the distributed build scripts. I created a constant '__cVersion__' in my rollup.config.js script and tried to use the 'rollup-plugin-modify', but that only updated my main.js and App.svelte code (the files being compiled). I also tried the '#rollup/plugin-replace' plugin with the same results. I needed the files I was also copying (not building) from src to public to also to have instances of '__cVersion__' replaced in the scripts.
The following was my initial rollup.config.js export function (the string replacement that did not work):
export default {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/build/bundle.js'
},
plugins: [
// this only seems to work on the main.js and .svelte files
modify({
'__cVersion__': 'c0.1.19'
}),
svelte({
dev: !production,
css: css => {
css.write('public/build/bundle.css');
}
}),
copy({
targets: [{
src: 'src/bs4.4.1.css',
dest: 'public/'
},
{
src: 'src/sw.js',
dest: 'public/'
},
{
src: 'src/index.html',
dest: 'public/'
},
{
src: 'src/manifest.json',
dest: 'public/'
},
{
src: 'src/images/*',
dest: 'public/images/'
}
]
}),
resolve({
browser: true,
dedupe: ['svelte']
}),
commonjs(),
// cache files
workbox({
mode: 'injectManifest',
options: {
swSrc: 'src/sw.js',
swDest: 'public/sw.js',
globDirectory: 'public',
globPatterns: [
'**/*.{html,json,js,css,png,map}',
'./manifest.json',
'./images/**',
'./bs4.4.1.css',
'./index.html'
]
}
}),
!production && serve(),
!production && livereload('public'),
production && terser()
],
watch: {
clearScreen: false
}
};
By default, the rollup-plugin-copy plugin will trigger on rollup's buildEnd hook.
Setting the hook to writeBundle fixed this issue for me, like so:
copy({
targets: [
{
src: ...,
dest: ...
},
...
],
hook: "writeBundle",
}),
I decided to take a different approach since I realized the build process needed to complete before I tried to replace '__cVersion__' in the files. After some trial and error I settled on this code:
https://github.com/kuhlaid/svelte2/releases/tag/v0.1.7
If you search the source code for '__cVersion__' you will see where I am adding the file revision string to try and force a file cache update...however, that didn't fully fix the issue.
I then looked at the service worker (sw.js) and realized the Workbox 'injectManifest' was actually handling the file revisions. The only problem with my current setup was that I had added '__cVersion__' constants to my scripts, but Workbox never saw the replacements since Workbox processed the service worker before I replaced the constants.
What I probably need to do is copy the src files to a 'staging directory' where I can replace the 'cache' constants in the scripts and then run the build rollup off of the staging files. This 'should' cause Workbox to treat the files as updated and thus assign them different revision numbers in the service worker file. I will try and update this thread when I have that issue worked out.
I am using Grunt + browserSync + grunt-php. The server starts normally. The problem is that whenever I make changes to PHP files, the changes are not reloaded automatically in browser. I have to manually reload the page despite having the settings in place. Been trying to solve this issue for the past 1 week, but no success. Tried out other online sources, but didn't help either. Please help.
Directory structure:
my_app/
src/
index.php
about.php
dist/
Gruntfile.js:
"use strict";
module.exports = function (grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
watch: {
php: {
files: ['src/**/*.php']
}
},
browserSync: {
dev: {
bsFiles: {
src: 'src/**/*.php'
},
options: {
proxy: '127.0.0.1:8010', //our PHP server
port: 8080, // our new port
open: true,
watchTask: true
}
}
},
php: {
dev: {
options: {
port: 8010,
base: 'src'
}
}
}
});
grunt.registerTask('default', [
'php', // Using the PHP instance as a proxy
'browserSync',
'watch' // Any other watch tasks you want to run
]);
};
A kind soul helped me with the answer. I don't take credit for the answer and would like to share the solution so that it may help someone in need. Here it is:
1) Just make sure that you have the body tag in the PHP file that you want to reload.
2) Include the following JS code in the page:
<script id="__bs_script__">
//<![CDATA[
document.write("<script async src='/browser-sync/browser-sync-client.js?v=2.17.5'><\/script>".replace("HOST", location.hostname));
//]]>
</script>
I'm having an annoying issue where uglify (called through grunt-usemin) cannot parse the url for a package named flow.js. This package is installed as a dependency of ng-flow.
The url looks like this:
bower_components/flow.js/dist/flow.js
When I run grunt build, I get the following error:
Warning: Unable to read "dist/public/bower_components/flow.js" file (Error code: EISDIR). Use --force to continue.
Since I don't know much about usemin and uglify, I can't put my finger on the issue. My guess was that it breaks when it gets at the first "flow.js" of the url. So, I tried to install flow.js as flowjs in my bower.json, but since its a dependency of ng-flow it will still get flow.js when running bower update.
Can anyone tell me more about this error, or suggest me a workaround to rename the dependency package?
Edit: usemin part of the Gruntfile
useminPrepare: {
html: ['<%= yeoman.client %>/index.html'],
options: {
dest: '<%= yeoman.dist %>/public'
}
},
usemin: {
html: ['<%= yeoman.dist %>/public/{,*/}*.html'],
css: ['<%= yeoman.dist %>/public/{,*/}*.css'],
js: ['<%= yeoman.dist %>/public/**/*.js'],
options: {
assetsDirs: [
'<%= yeoman.dist %>/public',
'<%= yeoman.dist %>/public/assets/images'
],
// This is so we update image references in our ng-templates
patterns: {
js: [
[/(assets\/images\/.*?\.(?:gif|jpeg|jpg|png|webp|svg))/gm, 'Update the JS to reference our revved images']
]
}
}
}
grunt.registerTask('build', [
'clean:dist',
'injector:less',
'concurrent:dist',
'injector',
'wiredep',
'useminPrepare',
'autoprefixer',
'ngtemplates',
'concat',
'ngAnnotate',
'copy:dist',
'cdnify',
'cssmin',
'uglify',
'rev',
'usemin']);
For the records, the grunt file is generated using https://github.com/DaftMonk/generator-angular-fullstack
The problem is that Grunt can't distinguish between a directory ending in '.js' and a .js file.
The workaround will be to use a wildcard expression to grab the files in flow.js and to exclude the directory itself.
Edit: You will need to exclude flow.js from both usemin AND rev.
usemin: {
html: ['<%= yeoman.dist %>/public/{,*/}*.html'],
css: ['<%= yeoman.dist %>/public/{,*/}*.css'],
js: {
src: ['<%= yeoman.dist %>/public/{,*/}*.js', '!<%= yeoman.dist %>/public/bower_components/flow.js'],
},
...
rev: {
dist: {
files: {
src: [
'<%= yeoman.dist %>/public/{,*/}*.js',
'<%= yeoman.dist %>/public/{,*/}*.css',
'<%= yeoman.dist %>/public/assets/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
'<%= yeoman.dist %>/public/assets/fonts/*',
'!<%= yeoman.dist %>/public/bower_components/flow.js'
]
}
}
},
The problem is that Grunt can't distinguish between a directory ending in '.js' and a .js file.
This is a wrong statement. Grunt use minimatch lib to configure how to search for files, and there is a isFile filter that you can set to true to only search files. See grunt.file.expand
Following this link you will find an example with filter: 'isFile'.
By the way you should upgrade to usemin v3, as this issue seems to be solved as you can see in issue #474
You can see my patch here where I've migrate the project to usemin 3 and switch grunt-rev to grunt-filerev.
I'm experimenting with yeoman and bower.
I have created a yeoman webapp using the following command
yo webapp
I want to use jqueryui so I have installed it using bower:
bower install jquery-ui --save
This works fine, but the jQuery UI component doesn't contain a javascript file with "all" the components, it just contains a lot of javascript files, one for each component.
Should I include only the javascript files that I need? Or should I do something else before using jQuery UI?
Thanks for the tips!
Added jquery-ui in dependencies of bower.json (or component.json) along with jquery.
{
…,
"dependencies": {
"jquery": "~1.9.1",
"jquery-ui": "~1.10.3",
...
},
…
}
Install them:
bower install
Then, added path to jqueryui In main.js and require it:
require.config({
paths: {
jquery: '../components/jquery/jquery',
jqueryui: '../components/jquery-ui/ui/jquery-ui',
…
},
shim: {
jqueryui: 'jquery',
…
},
…
});
require(['app', 'jquery', 'jqueryui', 'bootstrap'], function (app, $) {
'use strict';
...
});
It works for me.
In the latest jQuery UI bower component as we speak (v. 1.10.3), you can do the following:
For the CSS themes, include the following link:
<link rel="stylesheet" href="bower_components_path/jquery-ui/themes/base/jquery-ui.css">
To get the most components and widgets of jQueryUI running, include the following script:
<script src="bower_components_path/jquery-ui/ui/jquery-ui.js" ></script>
For reference, bower install jquery-ui --save would add the jquery-ui.js dependency to the project, but not the styles. For that I needed to add to the bower.json file an overrides section, as below
{
...,
"dependencies": {
...,
"jquery-ui": "^1.11.4" // already added with --save from bower install command
},
...,
"overrides": {
"jquery-ui": {
"main": [
"themes/smoothness/jquery-ui.css",
"jquery-ui.js"
]
}
}
}
References:
https://stackoverflow.com/a/27419553/4126114
https://github.com/taptapship/wiredep/issues/86
I would just include the files that I need or use the default custom build in the folder (which I believe has all the components) if you require everything or if it's just for experimentation.
<script src="components/jqueryui/ui/jquery-ui.custom.js"></script>
At this time bower pulls down the entire repo and since (from their website) "bower is just a package manager" anything else needed like concatenation or module loading is handled by other tools like sprockets/requirejs.
References:
Using packages with bower on homepage http://bower.io/
Dissusion about bower and pulling entire repos
https://github.com/bower/bower/issues/45
You could use requirejs.config's shim property to achieve your goal:
requirejs.config({
shim: {
'jquery.ui.sortable': {
deps: ['jquery', 'jquery.ui.core', 'jquery.ui.widget', 'jquery.ui.mouse', 'jquery.ui.position'],
exports: '$'
}
}
});
We specified, that jquery.ui.sortable, when required in your project, needs to load and execute the modules listed under deps first, before being executed itself.
Unfortunately, this still produces a race condition... But that is generally how one would go about this (: