Use html-webpack-plugin to inject bundle script tags into <asp:Content> tag in legacy .aspx files with master page? - asp.net

I'm fairly familiar with webpack and the html-webpack-plugin and have used them on a couple of other (SPA) projects. But in this new project I have to convert a legacy multi-page website to use webpack. There is a custom asp.net handler (ashx) that currently bundles (and minifies in prod builds) registered scripts by placing a comma separate list of script names on the query string of the .ashx reference in a script tag: <script src="Script.ashx?i=jquery,jquery-ui,...">.
One of the problems is that almost all the pages use a master page. So, there is no <body> tag to use for the html-webpack-plugin.
If I was dealing with a small number of entries I would have no problem using a few html-webpack-plugin templates to inject the scripts and place the output files in the correct place in the project folder structure. But there are 50 aspx pages in various locations in the project folder structure. So I would very much like to avoid maintaining separate templates for all of those pages.
But given that there no <body> tags in any of these files, how do I inject the scripts into the desired place?
I've built a custom code generator to read all the aspx pages in the project and find the Script.ashx references. It then parses the comma separated query string and generates a companion .js file with one import statement for each of the referenced scripts. These companion .js files will be what are referenced in the webpack "entry" array. So, for instance /home.aspx gets a companion /home-entry.js file. That file is in the webpack config: entry { "home" : "./home-entry", ... }. And the corresponding Script.ashx is commented out in the source aspx page. I'm also code generating the webpack entry array and the html-webpack-plugin references for each entry into the plugins array in the webpack config.
Home.aspx (snippet):
<asp:Content ID="Content6" ContentPlaceHolderID="footerPlaceHolder" runat="server">
<script type="text/javascript" src="/Script.ashx?i=jquery,jquery-migrate,jquery-ui,jquery-watermark,popr,acrobat-detection,pdflinkfix,device,navbar,jqdialoghelper,home&v=<%= "" + MyNameSpace.Scripts.ScriptHelpers.AssemblyVersion %>"></script>
</asp:Content>
Entry example:
"home" : "./home-entry",
Plugins example:
new HtmlWebpackPlugin( {
chunks: ['home-entry'],
alwaysWriteToDisk: true,
filename: "./home.aspx",
inject: 'body', // or what?
chunkSortMode: "dependency",
hash: true
} ),
home-entry.js:
import '/Scripts/jquery-3.3.1.min'
import '/Scripts/jquery-migrate-min'
import '/Scripts/jquery-ui-1.12.1.min'
import '/Scripts/jquery.watermark.min'
import '/Scripts/popr/popr'
import '/Scripts/acrobat_detection'
import '/Scripts/PDFLinkFix'
import '/Scripts/Device'
import '/Scripts/NavBar'
import '/Scripts/jqDialogHelper'
import '/Scripts/Home'
Expected result: The big problem is that I cannot figure out if there is a way to tell html-webpack-plugin to inject into a specific tag. I.e. what I want it to do is find the specific <asp:Content ID="Content6" ContentPlaceHolderID="footerPlaceHolder" runat="server"> tag and inject the script tags into it. Note that there are other <asp:Content> tags that have different ContentPlaceHolderID values. So html-webpack-plugin has to find the one with ContentPlaceHolderID="footerPlaceHolder".
Actual result: I believe with a default html-webpack-plugin options, in the absence of a <body> tag, the plugin will place the scripts at the end of the file. Which will confuse asp.net.

It sounds like the codebase I am working on has a similar setup to yours. I searched for a solution for quite a while to no avail. I ended up writing my own webpack plugin which handles this situation for me now. It defaults to writing to an index.aspx page but you can specify whatever type of page and/or location you'd like it to write to.
You can check it out here. Feel free to install and use if you'd like. Its not super polished or anything but its working well for our setup.
https://github.com/pckessel/MPAInjectionWebpackPlugin

Related

How to import styles from css files into an .ejs template and send it over email?

What i want to do:
Send one email with styles;
Have separate files, one for my .ejs template and one .css for styles that correspond to that template.
What the problem is:
If i create separate routes for the styles and insert it into the HTML, the user will need to "authorize" the download of these styles;
If i just import like normal HTML, it will not go through the email.
What am i using:
Express.js
Typescript
EJS
nodemailer
What is my file structure like at the moment:
src
views
index.ejs
style.css
internal_view_group
style.css
internal_template_name
style.css
locale
pt-br.ejs
en-us.ejs
public_view_group
style.css
public_template_name
style.css
locale
pt-br.ejs
en-us.ejs
Ok, if the directory is a little messy or hard to understand, let me break it down for you:
Folder "views" contains all my templates and styles, it also contains my index.ejs, that's what i'll call for when rendering the HTML, this index will then call styles from the group, template and then call the HTML from template, considering the language it uses. It also has a style.css, a global stylesheet that must be used in all templates, regardless of it's group.
Folder "internal_view_group" is a group of templates, for example, we have an internal group, templates used for internal communication, and a public group, used for the general public communication. It also has a default style.css, that is applied to every template inside this group.
Folder "internal_template_name" is a template, it has it's locales inside "locales" folder and a stylesheet that must be applied onto the called locale.
How can i have this level of organization on my code, having separate files and still work, considering the section "The problem is"?
Since you use your index.ejs to call all other templates and styles, you can also directly inject the styles into this index.ejs using the <%- %> tag from EJS.
What this tag does: import a file (usually an .EJS), read it's content and output it unscaped to the HTML.
so what you want to do into the index.js is something similar to this:
<head>
<%-
'<style>'
+ include(`./style.css`)
+ include(`./${template_group}/style.css`)
+ include(`./${template_group}/${template_name}/style.css`)
+ '</style>'
%>
</head>
<body>
<%- include(`./${template_group}/${template_name}/locale/${locale}.ejs`) %>
</body>
This will output all style RAW contents into the code (not a minified version).
The reason for the style tag being inside strings is that VSCode may report an error in your code because of EJS tags inside style sections, with this, only the < at the start of <style> will be reported as "wrong", but with nothing at output terminal telling you.
If you find that the final size of HTML code that you want to send is too large and may become a problem, you can use javascript prototype.String functions after the include, like include('./style.css').replaceAll(' ', ''), to replace all spaces but leave line breaks (not tested)

Using Angular 2 components on existing ASP.NET web pages

I am trying to move the front-end part of a website to Angular 2.
Currently I have really big ASP.NET 4.5 website.
I would like to create a separated Angular 2 project as it will be growing with time (and hopefully replace the asp.net)
For now I would like to create this project in Visual Studio (2015) and use some angular components or modules in the actual ASP.NET website.
Does the angular AppModule as to be in the ASP website?
I made some research but could not find answers or examples.
Am I gonna be able to do this relying on npm and system.js ? I saw that a lot of people are using gulp for copying file.
Is there a "right" way of doing this?
Any help would be highly appreciated
This question is not specific to Visual Studio, but yes, you can certainly accomplish this.
As you have suggested you can and should maintain the Angular application as a separate project.
The only additions that you need to make to your .aspx page are
including SystemJS and its configuration via script tags in that page or in its Master Page (You can also do this dynamically for CMS pages and using all sorts of other strategies). For example
<script src="loction-of-systemjs.js"></script>
<script src="loction-of-systemjs.config.js"></script>
Adding a markup tag with the selector corresponding to the app's root element, say 'my-embeddedable-widget', to your .aspx markup. For example
<my-embeddedable-widget>Loading...</my-embeddedable-widget>
Importing your application via SystemJS.import from a script tag embedded in the page containing the component selector above. For example
<script>
SystemJS.import('my-embeddedable-widget')
.catch (function(e) {
console.error(e);
}); // not using .bind or => here since aspx tends to imply older browser support
</script>
Note that this presupposes two things
that the 'my-embeddedable-widget' is set up in your SystemJS configuration. For example
SystemJS.config({
packages: {
'my-embeddedable-widget': {
main: 'main.ts' // just an example, could be main.js or anything really
}
}
});
If it is not you can add the config entry above as appropriate for your app (strongly recommended) or just import it directly from the path to the app's entry point such as e.g. my-embeddedable-widget/main.ts or my-embeddedable-widget/main.js.
That the entry point of your widget declares all of its platform level dependencies, such as zone.js and likely various polyfills. For example
my-embeddedable-widget/main.ts
import 'zone.js';
import 'core-js';
// ...
import {platformBrowserDynamic} from '#angular/platform-browser-dynamic';
// ....
This means that SystemJS will automatically load them when your widget is requested. While you could bring them in via separate script tags as we do with the loader itself, making them explicit dependencies of our widget by using ES Modules improves maintainability and allows us to defer loading them until they are required. Furthermore it helps further decouple the widget from the .aspx page. However, if other JavaScript on the page requires these polyfills, you may need to adjust this approach (especially with respect to zone.js because it monkey patches window.Promise)

How to handle CSS with meteor?

I am building a test app to learn how to organize multiple files with METEOR.
I have a head.html and inside I have the following link to my custom CSS:
<!-- Custom CSS -->
<link type="text/css" rel="stylesheet" href="/stylesheets/globals/style.css"/>
Very normal, Yet I have trouble to make that working.
Here is my app directory:
-app folder
---client
-----head.html
-----index.html
-----stylesheets
-------globals
---------style.css
I know it seems to be a very basic question but I can not figure it out.
Basically you have 2 ways of inserting CSS in a Meteor project :
Using the Meteor build tool to automatically concatenate and minify all your CSS files living in the client/ directory : in this case you don't need to import your stylesheets using a link tag in the head. This is perfect for vital CSS files that your app should load when started.
Example : put your CSS file under client/stylesheets/globals/style.css and that's it, no need to import it, it's automatically injected in your project by Meteor.
Using the classic way of importing stylesheets in a web application : you can put your CSS files inside the public/ directory and they will be served by your app server. In this case the Meteor build process will be skipped so files won't be concatenated together nor minified. Use this method when you want to lazy load big CSS files only needed in a subpart of your app (for example admin section styling).
Example : put your minified CSS file under public/stylesheets/admin/style.css, and use something like iron:router to load the CSS file when hitting the admin route.
Router.route("/admin", {
// onRun hooks executed only once
onRun: function(){
// create a link taf holding a reference to our publicly served CSS file
var link=$("<link>",{
rel: "stylesheet",
href: "/stylesheets/admin/style.css"
});
// append to the head tag
$("head").append(link);
}
});

Integrate CKEditor in Meteor

I'm trying to use CKEditor in a meteor application:
My attemps:
Put CKEditor folder with all the files (js, css, lang, plugins and skins) in the public folder, include the reference to the javascript file (ckeditor.js) in the header and use the appropiate class in textarea elements. Failed because the editor only works if the textarea is in the body (in any template the textarea control remains unmodified).
Put the javascript files (ckeditor.js, config.js, styles.js) in client/lib/compatibility folder and the remaining files in the public folder. This time the application cant locate the files (skins, plugins, ...) because is looking for localhost:3000/client/lib/compatibility/ckeditor/ ...
Has anybody make this integration works before?
I got this working and wanted to post a solution for future visitors. First, you need to put everything from the CKEDITOR build download in the public folder. CKEDITOR comes with all sorts of stuff and references everything based on relative directories.
Your public folder should have a directory named ckeditor it should contain contain the following files and folders:
adapters
lang
plugins
skins
ckeditor.js
config.js
contents.css
styles.js
In your primary layout file reference CKEDITOR like so:
<head>
<script type="text/javascript" src="/ckeditor/ckeditor.js"></script>
<script type="text/javascript" src="/ckeditor/adapters/jquery.js"></script>
</head>
In your template:
<template name="yourTemplate">
<textarea id="content" name="content"></textarea>
</template>
Finally, in the rendered function of your template:
Template.yourTemplate.rendered = function() {
$('#content').ckeditor();
};
Normally, you would say this.$('#content').ckeditor() but that doesn't work because CKEDITOR is in your public folder. As a result, you need to the global reference to the #content element.
Put only the CKEditor files that you would've included in <head> inside a folder in client/lib, i.e. client/lib/ckeditor. That's all you need to do to get them served to the client: there's no need to reference anything in any <head> or anything like that. All .js and .css files that Meteor finds inside client are automatically concatenated and served to the client. This applies to any client-side library, not just CKEditor.
The next thing you need to do is cause CKEditor to be initialized on the pages that use it. Say you have a template called edit with a textarea with an ID of editor. And say you're also loading the CKEditor jQuery Adapter. Inside a JavaScript file within client, put:
Template.edit.rendered = function() {
$('#editor').ckeditor();
}
The key here is that the initialization happens after the textarea editor exists and is ready, because this code is executed after the edit template is fully rendered. It will be reexecuted anytime edit is rerendered. Any other client-side library is included and initialized similarly.
EDIT Image files referenced via .css are a pain in Meteor. The "proper" way to deal with them is to put them all under the folder public, in this case for example public/ckeditor. Then edit the CKEditor .css files so that all references to image URLs point to your new folder at the root, i.e. /ckeditor/image1.png etc. (leave out "public").

ASP.NET MVC - weird style being generated in release mode

I have this:
bundles.Add(new StyleBundle("~/Content/Styles/Default").Include("~/Content/Styles/Default/Site.css"));
On my sites i have this:
#section Styles
{
#Styles.Render("~/Content/Styles/Default"))
}
My _Layout.cshtml looks like this:
#RenderSection("Styles", true)
Everything looks good, eh? Well, not really. When i compiled my application in release mode, decided to publish it, this is what it renders:
<link href="/Content/Styles/Default?v=78dkNySP_xsiuzsgxCx_GGnnHzYS-B8nNdnXqcl47XI1" rel="stylesheet">
Instead of generating href to a file, it generates some kind of id? Guid? Why? O.o
This is how bundles work. It's main purpose is for you to combine multiple CSS (and JS files for that matter) files into one package. e.g. you no longer have to put all your css (and js) into one huge file. Just split it up into sections, then add it into your bundles, and it packages it up into one item. Less web requests, the faster your page load time.
e.g. Lets say you had 2 css files. One's the main, but you had one for your menu system.
bundles.Add(new StyleBundle("~/Content/Styles/Default").Include(
"~/Content/Styles/Default/Site.css",
"~/Content/Styles/Default/Menu.css"));
This would show up as a single call with the GUID type code (to prevent caching on file changes) on the URL. This URL will link to a minified and bundled css.
But my browser cannot read that! There is no physical path to a file!
It's a sort of virtual file. MVC's bundling uses the routing engine to point it to a combined and minified version of a particle bundle.

Resources