I am trying to get around an issue in my Angular website where when I add a page or make a change it does not show up to a user because of caching. Having done some reading I understand that what I want to do is to create a fingerprint.
I had a look around and it seemed like grunt-asset-fingerprint would do the trick. I have downloaded the package and tried to run it. However, I have two issues:
It only seems to copy one file into the assets mapping file (infact it was my bing authorisation file), I would have thought it would copy all of them?
I have no idea what to do next, do I need to add the fingerprint to the file names in order to avoid the caching problem? I could not find much documentation online.
Any help would be much appreciated! The relevant parts of my grunt config and the outputs of the assets.json file are below. Thanks!!
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
assetFingerprint: {
options: {
algorithm: 'md5',
},
dist: {
src: ["dist/**/*"],
dest: "dist/"
},
}
});
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-processhtml');
grunt.loadNpmTasks('grunt-contrib-imagemin');
grunt.loadNpmTasks('grunt-contrib-htmlmin');
grunt.loadNpmTasks('grunt-newer');
grunt.loadNpmTasks('grunt-asset-fingerprint');
};
assets.json
{
"dist/": "./dist-dec2ce4621028dc1bd5g081ea11d1aff"
}
I was able to successfully append the hash to my app.js file using this structure:
assetFingerprint: {
"options": {
"manifestPath": "dist/assets.json",
"findAndReplaceFiles": [
"index.html"
],
"keepOriginalFiles": false
},
"dist": {
"files": [
{
"expand": true,
"cwd": "dist",
"src": "app.js",
"dest": "dist"
}
]
}
}
This will also update the reference to app.js in my index.html file. You have to make sure the paths used in the files block align with your directory structure.
Refer grunt-rekai, this could solve file rename and have more options.
https://www.npmjs.com/package/grunt-rekai
Related
I'm working on a React application inside my NX Workspace.
Now I want to add sentry to my project. I already have a deploy configuration in my project.json. But I'm struggling with adding the step to upload the source maps.
Here is my project.json
"deploy": {
"executor": "nx:run-commands",
"options": {
"parallel": false,
"commands": [
{
"command": "nx run my-app:build:{args.target}",
"forwardAllArgs": true
},
{
"command": "echo Run {args.target} deployment on {args.server}",
"forwardAllArgs": true
},
{
"command": "rsync -avz --progress --delete dist/apps/my-app/ {args.user}#{args.server}:{args.path}",
"forwardAllArgs": true
},
{
"command": "echo my-app deployed to {args.target}",
"forwardAllArgs": true
}
]
},
"configurations": {
"production": {
"args": "--target=production --user=user --server=myserver.com --path=path/to/app"
}
}
}
Is there any example of how to perform the upload of the source maps using a nx workspace? Or do I have to create a custom script that handles everything and put it into my project.json as the second command (after build, before deploy).
Also, I'm not sure how to handle the version number of my application as NX does not provide a way to define version numbers for each application inside the workspace.
You'll have to create a custom webpack config on the Nx project: https://nx.dev/recipes/other/customize-webpack
and then follow the instructions on how to setup it on Sentry using webpack:
https://docs.sentry.io/platforms/javascript/guides/react/sourcemaps/uploading/webpack/
Your custom webpack should look like something like this:
const {merge} = require('webpack-merge')
const SentryWebpackPlugin = require('#sentry/webpack-plugin')
const nrwlConfig = require('#nrwl/react/plugins/webpack.js')
module.exports = (config) => {
// merge config from #nrwl/react first
nrwlConfig(config)
return merge(config, {
devtool: 'source-map', // Source map generation must be turned on
plugins: [
new SentryWebpackPlugin({
org: 'orgId',
project: 'projectId',
// Specify the directory containing build artifacts
include: './build',
// Auth tokens can be obtained from https://sentry.io/settings/account/api/auth-tokens/
// and needs the `project:releases` and `org:read` scopes
authToken: process.env.NX_SENTRY_AUTH_TOKEN,
// Optionally uncomment the line below to override automatic release name detection
// release: process.env.RELEASE,
}),
],
})
}
Please notice that unfortunately this is not a final answer, because with this I can upload the artifact, but the source maps are still not working on Sentry and I don't know why yet - but maybe this can be helpful somehow to you.
i have some files from node_modules they need to be saved in cache because i am getting this information
enter image description here
to get this information i audited with lighthouse this is the web https://www.webpagetest.org/
this is the final result, "cache static content" is the wrong
enter image description here
i am working with Gatsby, i found this information about my problem https://www.gatsbyjs.com/docs/caching/#javascript-and-css but i am not sure how can i use it or should i use another library? i dont have idea how can i cache
i think i should use these properties:
The cache-control header should be cache-control: public, max-age=31536000, immutable
this is my file gatsby-config
plugins: [
'gatsby-plugin-top-layout',
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'assets',
path: `${__dirname}/src/assets`,
},
},
`gatsby-plugin-image`,
'gatsby-transformer-sharp',
'gatsby-plugin-sharp',
{
resolve: 'gatsby-plugin-manifest',
options: {
},
},
{
resolve: `gatsby-plugin-offline`,
options: {
precachePages: ['/projects/'],
},
},
{
resolve: 'gatsby-plugin-material-ui',
my propuse to do this is solve this problem, there are a blank space when i reload the web application
blank space
Gatsby generates static content. It doesn't handle sending HTTP responses. That's going to be a web server (or in your case firebase).
Checkout the docs for sending headers on firebase
https://firebase.google.com/docs/hosting/full-config#headers
You'll need something like:
"hosting": {
"headers": [ {
"source": "**/*.#(eot|otf|ttf|ttc|woff|font.css)",
"headers": [ {
"key": "cache-control",
"value": "public, max-age=31536000, immutable"
} ]
} ]
}
This caches font files which is generally safe because fonts change very rarely.
You can add js and css to that list if you want as well. Keep in mind that you'll need to change the names of an js and css files or the browser will wait 1 year before reloading them.
We are in the process of upgrading an AngularJS application to Angular with the incremental approach: we would like to be able to create new components in Angular and upgrade existing AngularJS components one by one, all this still with a functional application during the process.
We use the official documentation as well as several articles about hybrid Angular/AngularJS applications on real world applications.
Here are our attempts and the errors we get.
Context
AngularJS 1.7.3
Angular 6.1.7
Typescript 2.7.2
angular-cli
First steps
upgrade to AngularJS 1.7
remove Grunt and use angular-cli
use ngUpgrade (app.module.ts and app.module.ajs.ts)
Moving to Typscript: dealing with errors
That's the official process: rename .js files to .ts.
We then moved from Node require() to TypeScript module loading (var ... = require --> import ... = require)
Ideally, we should correct all the typing errors due to using the TypeScript compiler.
But the TypeScript doc states that's it's possible to do an incremental migration: being tolerant to TypeScript errors at the beginning in order to compile the js code without modifications (and stricter later on after fixing the code).
To achieve this, we used the awesome-typescript-loader instead of tsc to get theses options: transpileOnly, errorsAsWarnings (this requires the use of angular-builders/custom-webpack).
The options allow to pass the compilation, but it appears that the compilation is done twice: first without errors (warnings or not), but second with errors... so the build fails. How can we run only the first compilation?
Alternative attempt: keeping .js files, errors in importing and bootstrapping
We tried also an unofficial and unusual way to migrate the js code incrementally, that is keeping the original .js files alongside new .ts files.
We got some errors at compilation or application loading, related to importing AngularJS and to module management. Indeed the TypsScript module documentation states that:
Some libraries are designed to be used in many module loaders, or with no module loading (global variables). These are known as UMD modules. These libraries can be accessed through either an import or a global variable. ... can be used as a global variable, but only inside of a script. (A script is a file with no imports or exports.)
What we noticed:
in .js files: access to the AngularJS global angular (if we remove require("angular")) - script mode
in .ts files: we can't use import from angular, otherwise we get the error angular.module is undefined - module mode
With this in mind, we made the application compile and load in the browser without errors, but at the end:
this.upgrade.bootstrap(document.body, [App])
generates an error on AngularJS bootstrapping:
Angular 1.x not loaded !
How to fix this? It may be because we didn't import AngularJS in the TypeScript module way to avoid the previous error?
The official documentation mentions angular.IAngularStatic (still get an error)?
We can try also setAngularJSGlobal() ? Used when AngularJS is loaded lazily, and not available on window
By the way what is the difference between using [App] or ["App"] when calling bootstrap()?
... Since we are new to this upgrade process, we may be doing completely wrong things. Thank you for sharing your experience!
Configuration files
angular.json
{
"$schema": "./node_modules/#angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "acd-banner-multicanal",
"projects": {
"acd-banner-multicanal": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"architect": {
"build": {
"builder": "#angular-devkit/build-angular:browser",
"options": {
"outputPath": "target",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "./tsconfig.json",
"assets": [
"src/i18n",
"src/conf/conf.txt",
"src/conf/conf_DEFAULT.txt",
"src/systemjs.config.js",
{ "glob": "font-banner*", "input": "./node_modules/elq-font-icon/build/", "output": "/assets/fonts" },
"src/assets/images",
{ "glob": "system.js*", "input": "./node_modules/systemjs/dist/", "output": "/assets" },
"src/assets/images",
{ "glob": "**/*", "input": "./node_modules/tinymce/plugins", "output": "/assets/tinymce/plugins" },
{ "glob": "**/*", "input": "./node_modules/tinymce/skins", "output": "/assets/tinymce/skins" }
],
"styles": [
"src/assets/styles/style.less"
],
"scripts": [
"./node_modules/jquery/dist/jquery.js",
"./node_modules/jquery-ui-dist/jquery-ui.js"
]
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"aot": true,
"buildOptimizer": true
}
}
},
"test": {
"builder": "#angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "./karma.conf.js",
"scripts": [],
"styles": [
"src/assets/main.less"
],
"assets": [
"src/i18n",
"src/favicon.ico"
]
}
},
"lint": {
"builder": "#angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"acd-ihm-angular-e2e": {
"root": "e2e/",
"sourceRoot": "e2e",
"projectType": "application",
}
},
"defaultProject": "acd-banner-multicanal",
"schematics": {
"#schematics/angular:component": {
"styleext": "less",
"lintFix": true
}
}
}
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"outDir": "./target",
"sourceMap": true,
"experimentalDecorators": true,
"allowJs": true,
"baseUrl": "./",
"lib": [
"es2017",
"dom"
],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true,
"paths": {
"angular": ["node_modules/angular/angular"]
}
},
"include": [
"src/**/*"
],
"exclude": [
"src/**/*.spec.ts"
]
}
As for the angular 1.x not loaded error;
Did you install angularJS in the new application?
$ npm install --save angular#1.7.3 \
#types/angular
In the angular.json file you need to include the script:
"scripts": [
"../node_modules/angular/angular.js",
//etc...
],
Here's an example of upgrading an application that seem similar to what you have.
Alternatively you can bring in angular into the import chain by importing it in main.ts;
import * as angular from 'angular';
This might be a better option since it makes angular cli / webpack aware of angularJS and may prevent errors such as "WARNING: Tried to Load Angular More Than Once" that may arise if some other component (such as the hybrid router imports angular).
I confirm that the answer works, we've been able to run our application in hybrid mode. In fact, in AngularJs, we used grunt and browserify, and we had packaged some libraries using the package.json browser field. To do the same, we had to declare the libraries to load in the browser in angular.js / build.options.scripts:
"scripts": [
"./node_modules/jquery/dist/jquery.js",
"./node_modules/jquery-ui-dist/jquery-ui.js",
"./node_modules/moment/moment.js",
"./node_modules/bootstrap/dist/js/bootstrap.js",
"./node_modules/eonasdan-bootstrap-datetimepicker/src/js/bootstrap- datetimepicker.js",
"./node_modules/bootstrap-tour/build/js/bootstrap-tour.js",
"./node_modules/angular/angular.js",
"./node_modules/ng-table/bundles/ng-table.js"`
]
Thanks a lot.
That may be useful to add in the Angular documentation? Indeed, the examples given in https://angular.io/guide/upgrade#bootstrapping-hybrid-applications are based on SystemJS, whereas we just use Webpack (already used by Angular).
Indeed, there is an issue about the angular doc, the migration doc is not yet updated for angular-cli (that's why it is about SystemJS).
I am trying to test Angular using jasmine:
My Folder structure:
My chutzpah.json file:
{
"Framework": "jasmine",
"TestHarnessLocationMode": "Custom",
"TestHarnessDirectory": "../AngularExample",
"TestHarnessLocationMode": "SettingsFileAdjacent",
"TestHarnessReferenceMode": "AMD",
"References": [
{"Path": "../AngularExample/Controller","Include": "app.js"} ],
"Tests": [
{ "Path": "/Tests/Specs" }
],
"AMDBasePath": "../AngularExample/Controller"
}
I am getting below error message:
My code files are referred from here(reffering example from https://gist.github.com/blesh/8846528
It doesn't look like you are referencing Angular.js library itself. Please take a look at the Angular samples with Chutzpah and see if that helps.
I have an angular app setup that's using grunt, but I'd like to be able to use grunt as a preprocessor to replace variables and I haven't been able to find anything that matches my needs.
For instance, if I change the name of my main app module to "someAppName" in a config file, I'd like to just use something like "ENV.APP_NAME" in various html and js files and have that replaced by an appropriate value for that environment.
Ideally I'd like to have a config file somewhere along these lines, either as a .json file or using module.exports to expose an object, which specifies values for different environments:
{
APP_NAME:{
dev: "someAppDev",
prod: "someApp"
},
API_BASE:{
dev: "localhost:8000/",
prod: "https://www.some-site.com/api/"
}
}
and then I could create a grunt task and pass it either "dev" or "prod" to have it run the preprocessor and replace each instance with the corresponding value. I've found this https://github.com/STAH/grunt-preprocessor but the examples are confusing and I don't think it's quite what I'm looking for.
Is there anything like this that allows you to create preprocessed environment variables and read them from an external config file, or am I forced to build my own grunt plugin? Has anyone achieved something similar with grunt?
EDIT: I've begun building a grunt plugin for this specific task, once it's done and tested I'll post it up on npm
Use grunt-ng-constant.
Npm install this plugin:
npm install grunt-ng-constant --save-dev
Then in grunt write to config file:
ngconstant: {
// Options for all targets
options: {
space: ' ',
wrap: '"use strict";\n\n {%= __ngModule %}',
name: 'config',
},
// Environment targets
development: {
options: {
dest: '<%= yeoman.app %>/scripts/config.js'
},
constants: {
ENV: {
myvar1: 'Hello from devel',
myname: 'John Doe'
}
}
},
production: {
options: {
dest: '<%= yeoman.dist %>/scripts/config.js'
},
constants: {
ENV: {
myvar1: 'Hello',
myname: 'John Doe'
}
}
}
},
Then add to grunt task:
grunt.task.run([
'clean:server',
'ngconstant:development',
'connect:livereload',
'watch'
]);
Running task would generate config.js with constants defined in gruntfile.
Then add config.js to your index.html:
<script src="/scripts/config.js" />
Inject it to your app:
var app = angular.module('myApp', [ 'config' ]);
And inject into controller:
.controller('MainCtrl', function ($scope, $http, ENV) {
console.log(ENV.myvar1);
});
You can set different variables for production and different for development, by setting it in gruntfile and setting ng:production or ng:development.
Refer this article explaining the procedure for more information.
You could do something making use of grunt-string-replace while loading some config values into Grunt from a JSON file. Suppose you had this in myconfig.json:
{
"appName": "MyGrowth",
"version": "1.1.2",
}
Then perhaps load the config into Gruntfile.js from JSON with something like:
grunt.initConfig({
myConfig: grunt.file.readJSON('myconfig.json'),
// ... the rest of the stuff in your initConfig goes sure
}
Then you would have some sort of task like this maybe:
'string-replace': {
version: {
options: {
replacements: [
{
pattern: /MY_VERSION/ig,
replacement: '<%= myConfig.version %>'
}
]
},
files: [{
expand: true,
cwd: 'src/',
src: '**/*.js',
dest: 'dist/'
}]
}
}
That would make actual changes to the files if you had 'src' and 'dest' in the same folder, otherwise it would put the processed files in the dest folder.
I've not actually used this recipe for preprocessing, but I use it for other types of things like replacing tags in a framework with the app name as configured in package.json.
Hope it helps.
Alright I ended up building a grunt plugin just for this task because it's something that I really needed. It allows you to place environment variable definitions in a .json file like I described in my original question and replaces all instances in a specified file or list of files.
https://github.com/ejfrancis/grunt-envpreprocess
So if you have something like this
HTML
<head>
<title>ENV.APP_NAME</title>
</head>
<script src="ENV.API_BASE/user/create">
JS
var version = "ENV.APP_VERSION";
alert(version);
The task will replace it with this
HTML
<head>
<title>AppDev</title>
</head>
<script src="http://localhost:8000/user/create">
JS
var version = "0.1.0";
alert(version);