Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Experience to migrate JS modules to different bundler system

Sean Hsieh
December 18, 2019

Experience to migrate JS modules to different bundler system

The topic for F2E & FGBA Meetup December 2019

Sean Hsieh

December 18, 2019
Tweet

More Decks by Sean Hsieh

Other Decks in Technology

Transcript

  1. 1
    Experience to migrate JS modules to
    different bundler system
    F2E & FGBA Meetup December 2019
    謝任軒, Sean Hsieh
    Software engineer @ Garmin International

    View full-size slide

  2. 2
    Agenda
    • Self-introduction
    • Dilemma
    • What exactly do we have to do?
    • Building artifact
    • Recap

    View full-size slide

  3. 3
    Self-introduction

    View full-size slide

  4. Maybe we can do something like…
    AMD
    module
    AMD
    module
    AMD
    module
    AMD
    module
    moduleA.js
    moduleB.js
    moduleC.js
    moduleD.js
    layout.cshtml
    Index.cshtml
    Compile
    AMD
    module
    {Common.js
    moduleA.js
    moduleB.js}
    {Map.js
    moduleC.js
    moduleD.js}
    Non-AMD
    module
    Non-AMD
    module
    Common.js
    Map.js
    Boss: How to improve the module system?
    Require.js
    define/
    require
    (AMD)
    BundleConfig.cs
    Embedded
    module
    7
    Requirejs
    config
    1. To reduce the bundle size (We need module dependency management)
    2. To reduce the number of requests from the client (We need common dependencies extracting)
    3. To improve the development environment (We need plugin supports)
    It’s a 10-year ASP.NET MVC project …
    Well.. that’s change the module bundler

    View full-size slide

  5. The choices of the module bundler
    8
    .js .js .js .js
    .js .js
    .js
    • require('modules') in the browser
    by bundling up all of your dependencies.
    • Github stars: 13 k
    • Github forks: 1.1 k
    • Companies:
    • A bundler for javascript and friends.
    Packs many modules into a
    few bundled assets
    • Github stars: 52.1 k
    • Github forks: 6.6 k
    • Companies:
    Browserify
    Webpack
    Gulp
    Gulp
    Npm scripts

    View full-size slide

  6. AMD
    module
    AMD
    module
    AMD
    module
    AMD
    module
    layout.cshtml
    Index.cshtml
    Task runner
    AMD
    module
    moduleA.js
    moduleB.js
    moduleC.js
    Non-AMD
    module
    Non-AMD
    module
    map.bundle.js
    Webpack
    config.js
    Boss: what’s the efforts for migrating to Webpack?
    Webpack.js
    entry.bundle.js
    Embedded
    module
    9
    Well.. What do I exactly have to do in this case?
    The efforts depend on the previous JS modules,
    previous module loader’s configuration,
    and the previous bundler system
    Shimming

    View full-size slide

  7. 10
    What exactly do I have to do?

    View full-size slide

  8. AMD
    module
    AMD
    module
    AMD
    module
    AMD
    module
    layout.cshtml
    Index.cshtml
    Task runner
    AMD
    module
    moduleA.js
    moduleB.js
    moduleC.js
    Non-AMD
    module
    Non-AMD
    module
    map.bundle.js
    Webpack
    config.js
    Phases
    Webpack.js
    Shimming
    entry.bundle.js
    Embedded
    module
    11
    AMD
    module
    Phase 1. Make the embedded modules stand-alone
    Phase 2. Move the module loader’s configurations to webpack.config.js
    Phase 3. Write the entry bundle according to the contents of the page
    Phase 4. Generate Webpack bundles with the task runner
    Phase 5. Replace bundles with Webpack bundles
    • Transition period
    • Ready to apply Webpack

    View full-size slide

  9. AMD
    module
    AMD
    module
    AMD
    module
    AMD
    module
    layout.cshtml
    Index.cshtml
    Task runner
    AMD
    module
    moduleA.js
    moduleB.js
    moduleC.js
    Non-AMD
    module
    Non-AMD
    module
    map.bundle.js
    Webpack
    config.js
    Phases 1
    Webpack.js
    Shimming entry.bundle.js
    Embedded
    module
    12
    AMD
    module
    Phase 1. Make the embedded modules stand-alone
    Phase 2. Move the module loader’s configurations to webpack.config.js
    Phase 3. Write entry bundle according to the contents of the page
    Phase 4. Generate Webpack bundles with the task runner
    Phase 5. Replace original bundles with Webpack bundles

    View full-size slide

  10. • The mechanism of ScriptBundle and Webpack are quite different
    Index.cshtml
    Common.bundle.js
    Why can’t we use embedded modules?
    Embedded
    modules
    Stand-alone Modules
    13
    • Webpack can’t resolve modules from html/cshtml files
    import attach
    Webpack
    resolve
    Stand-alone modules
    resolve

    View full-size slide

  11. • An embedded module with variables from the controller
    Implementations: an example from ASP.NET MVC
    14
    ...
    @using Newtonsoft.Json
    ...
    <br/>define('config', function () {<br/>var config = {<br/>name: @Model.Name,<br/>Types: @Html.Raw(JsonConvert.SerializeObject(Model.Types)),<br/>Marks: @Html.Raw(JsonConvert.SerializeObject(Model.Marks)),<br/>Data: @Html.Raw(JsonConvert.SerializeObject(Model.Data))<br/>}<br/>return config;<br/>});<br/>
    @{
    dynamic jsonObj = new Linq.JObject();
    jsonObj.name = Model.Name;
    jsonObj.data = JsonConvert.SerializeObject(Model.Data) == "null" ?
    null : Linq.JObject.FromObject(Model.Data);
    jsonObj.marks = JsonConvert.SerializeObject(Model.Marks) == "null" ?
    null : Linq.JArray.FromObject(Model.Marks);
    jsonObj.MessageTypes = JsonConvert.SerializeObject(Model.Types) == "null" ?
    null : Linq.JArray.FromObject(Model.Types);
    string mapConfig = JsonConvert.SerializeObject(jsonObject).ToString();
    }
    @Html.Hidden("config", @config)
    define('config', function () {
    L_PREFER_CANVAS = true;
    return JSON.parse($("#config").val());
    });
    Index.cshtml
    Index.cshtml
    config.js
    Put the data into
    hidden fields
    Parse the data by using jQuery

    View full-size slide

  12. Concerns for the transition period
    15
    Note 1: Add a new JS file into the bundleConfig.cs before migrating to Webpack
    Note 2: It may occur error if you forgot to attach the bundle to other html/cshtml files
    common.js common.cshtml
    _layout.cshtml
    Config.js
    AController
    2. Add 3. [email protected]
    1.Move embedded modules to stand-alone modules
    Render. Pass data by model
    A.cshtml
    BController
    B .cshtml
    bundles.Add(new ScriptWithSourceMapBundle("~/bundles/Home.js").Include(
    …,
    "~/Scripts/config.js"
    ));
    Render
    define('config', function () {

    });
    [email protected]
    require(['config’], function (config) {

    });
    require

    View full-size slide

  13. Validation
    • One choice is to do functional tests with Katalon recorder, Selenium, Chrome driver,
    and NUnit
    16

    View full-size slide

  14. AMD
    module
    AMD
    module
    AMD
    module
    AMD
    module
    layout.cshtml
    Index.cshtml
    Task runner
    AMD
    module
    moduleA.js
    moduleB.js
    moduleC.js
    Non-AMD
    module
    Non-AMD
    module
    map.bundle.js
    Webpack
    config.js
    Phases 2
    Webpack.js
    Shimming entry.bundle.js
    Embedded
    module
    17
    AMD
    module
    Phase 1. Make the embedded modules stand-alone
    Phase 2. Move the module loader’s configurations to webpack.config.js
    Phase 3. Write entry bundle according to the contents of the page
    Phase 4. Generate Webpack bundles with the task runner
    Phase 5. Replace original bundles with Webpack bundles

    View full-size slide

  15. Map the searching path of modules
    18
    //requirejs.config
    requirejs.config({
    baseUrl: '/Scripts/',
    paths: {
    'knockout': 'knockout-3.0.0.debug',
    'koMapper': 'knockout.mapping-latest'
    }
    });
    • Solution 1: specific the path on the alias field
    //webpack.config.js
    const path = require('path');
    const config = {
    resolve: {
    extensions: ['.js', '.jsx'],
    alias: {
    knockout: path.resolve(__dirname, './Scripts/knockout-3.2.0.js'),
    koMapper: path.resolve(__dirname, './Scripts/knockout.mapping.js’),
    'common/svgSheet': path.resolve(__dirname, './Scripts/svgSheet.js’),
    common: path.resolve(__dirname, './Scripts/common’)
    }
    },
    };
    module.exports = config;
    • Original module loader (Require.js)
    Note 2: The order of the module’s name should be prior than
    folder’s name
    Module not found: Error: Can’t resolve ‘common/svgSheet’
    Note 1: It will lead conflicts if the node module has the same
    name as the local library’s name
    1. Remove that module in the package.json.
    2. npm install again.
    3. Put AMD modules’ name and path on the alias field
    const config = {
    resolve: {
    extensions: ['.js', '.jsx'],
    modules: [
    path.resolve(__dirname, './Scripts’), 'node_modules'
    ],
    alias: {
    knockout: "knockout-3.2.0.debug ",
    koMapper: 'knockout.mapping-latest',
    ...
    }
    }
    };
    module.exports = config;
    • Solution 2: search the module with the alias
    and modules field

    View full-size slide

  16. Make modules compatible with Webpack
    19
    define('someModule',
    [],
    function ()
    {
    ...
    if (require.defined('otherModule') && require('otherModu
    le').IsPublic) {
    successCallback && successCallback([]);
    return (completeCallback && completeCallback());
    }
    ...
    define('someModule',
    ['otherModule'],
    function (otherModule)
    {
    ...
    if (otherModule.IsPublic) {
    successCallback && successCallback([]);
    return (completeCallback && completeCallback());
    }
    ...
    1. Webpack provides basic functions for AMD modules
    2. We have to replace the require.js APIs with alternative solutions

    View full-size slide

  17. • Require.js shimming:
    Import non-AMD JS modules
    Module shimming
    20
    requirejs.config({
    shim: {
    'jquery': {
    exports: 'jQuery'
    }
    }
    });
    module.exports = {
    ...,
    resolve: {
    alias: {
    'jquery’: 'jQuery',
    ...
    }
    },
    plugins: [
    new webpack.ProvidePlugin({
    $: "jquery",
    jQuery: "jquery",
    ...
    })
    ]
    }
    • Webpack .config.js shim:
    Create globals which need to be exported for
    broken modules

    View full-size slide

  18. AMD
    module
    AMD
    module
    AMD
    module
    AMD
    module
    layout.cshtml
    Index.cshtml
    Task runner
    AMD
    module
    moduleA.js
    moduleB.js
    moduleC.js
    Non-AMD
    module
    Non-AMD
    module
    map.bundle.js
    Webpack
    config.js
    Phases 3
    Webpack.js
    Shimming entry.bundle.js
    Embedded
    module
    21
    AMD
    module
    Phase 1. Make the embedded modules stand-alone
    Phase 2. Move requiresjs.config to webpack.config
    Phase 3. Write entry bundle according to the contents of the page
    Phase 4. Generate Webpack bundles with the task runner
    Phase 5. Replace original bundles with Webpack bundles

    View full-size slide

  19. Get JS modules the from the nested layout
    22
    _layout.cshtml
    _logo.cshtml
    _nav.cshtml
    index.cshtml
    _common.cshtml
    Bundle.js
    Bundle.js
    Bundle.js
    JS modules
    JS modules
    JS modules
    1. Turn off the bundling
    (ScriptBundle)
    2. View page source

    3. Attach JS modules into the entry bundle

    View full-size slide

  20. Ways to import modules into Webpack bundle
    1. AMD require
    2. CommonJS synchronous loading
    3. CommonJS asynchronous loading
    23
    require(['map', 'react'], function (map, React) {
    map.init('inner-map-container');
    });
    var map = require('map');
    var react = require(‘react');
    map.init('inner-map-container');
    require.ensure([], function (require) {
    var mapStart = require('mapStart');
    var react = require('React');
    mapStart.init('inner-map-container');
    });
    4. CommonJS Lazy loading
    5. ES6 import
    require.ensure(['mapStart', 'react'], function (require) {
    var mapStart = require('mapStart');
    var react = require(‘react');
    mapStart.init('inner-map-container');
    });
    import mapStart from './mapStart';
    import react from './react';
    mapStart.init('inner-map-container');

    View full-size slide

  21. AMD
    module
    AMD
    module
    AMD
    module
    AMD
    module
    layout.cshtml
    Index.cshtml
    Task runner
    AMD
    module
    moduleA.js
    moduleB.js
    moduleC.js
    Non-AMD
    module
    Non-AMD
    module
    map.bundle.js
    Webpack
    config.js
    Phases 4
    Webpack.js
    Shimming entry.bundle.js
    Embedded
    module
    24
    AMD
    module
    Phase 1. Make the embedded modules stand-alone
    Phase 2. Move the module loader’s configurations to webpack.config.js
    Phase 3. Write entry bundle according to the contents of the page
    Phase 4. Generate Webpack bundles with the task runner
    Phase 5. Replace original bundles with Webpack bundles

    View full-size slide

  22. Append the Webpack task to the task runner
    1. Edit gulp.js file
    2. Run the task
    3. Use task runner explorer from Visual studio
    25
    const webpack = require("webpack-stream"),
    WEBPACK_CONFIG = require('./webpack.config.js'),
    src = require('gulp').src,
    dest = require('gulp').dest,
    pipeline = require('readable-stream').pipeline;
    function webpack_bundle(conf) {
    return pipeline(
    src('./Scripts/webpack/*.js'),
    webpack(conf),
    dest('Scripts/bundle/'));
    }
    exports.webpack = () => webpack_bundle(WEBPACK_CONFIG);
    gulp -b "D:\work\project" --color --gulpfile "D:\work\project\Gulpfile.js" webpack

    View full-size slide

  23. AMD
    module
    AMD
    module
    AMD
    module
    AMD
    module
    layout.cshtml
    Index.cshtml
    Task runner
    AMD
    module
    moduleA.js
    moduleB.js
    moduleC.js
    Non-AMD
    module
    Non-AMD
    module
    map.bundle.js
    Webpack
    config.js
    Phases 5
    Webpack.js
    Shimming entry.bundle.js
    Embedded
    module
    26
    AMD
    module
    Phase 1. Make the embedded modules stand-alone
    Phase 2. Move the module loader’s configurations to webpack.config.js
    Phase 3. Write entry bundle according to the contents of the page
    Phase 4. Generate Webpack bundles with the task runner
    Phase 5. Replace original bundles with Webpack bundles

    View full-size slide

  24. Replace bundles with Webpack bundles for
    ASP.NET
    1. Install Html-Webpack-Plugin
    2. Add Html-Webpack-Plugin in the plugin field in the webpack.config.js
    3. Create a template file
    4. Render the generated file from the original view
    27
    npm install Html-Webpack-Plugin --save-dev
    plugins: [
    ...,
    new HtmlWebpackPlugin({
    filename: './Views/_home.bundle.cshtml',
    template: './Views/BundlesTemp/_JsTemplate.cshtml',
    chunks: ['home'],
    inject: false
    })
    ]
    <% for (var chunk in htmlWebpackPlugin.files.chunks) { %>

    <%}%>
    @Html.Partial("~/Views/Bundle/_home.bundle.cshtml")

    View full-size slide

  25. 28
    Building artifact

    View full-size slide

  26. Boss: How about the build system?
    29
    {
    ...
    "scripts": {
    "gulp": "gulp",
    ...
    },
    Package.json

    ...





    .csproj


    ...
    precompiled
    ...




    ...



    ...


    TeamCity.pubxml
    .nuspec

    View full-size slide

  27. Recap
    1. The efforts of migration depends on the previous JS modules, previous
    module system’s configuration, and the previous bundler system
    2. Webpack can’t resolve embedded modules. We have to make them
    stand-alone
    3. The validation is important for refactoring layouts
    31

    View full-size slide

  28. 32
    Thanks!
    Any questions?
    You can find me at:
    [email protected]

    View full-size slide

  29. References
    • 從無到有建立 webpack 設定檔(一)
    • Passing serialized C# object in JSON to Razor Page
    • Webpack Code Splitting ‘Loading chunk failed’ error wrong file path
    • webpack解惑:require的五种用法
    • From Require.js to Webpack — Part 1 (the reasons)
    • xjamundx/blog-webpack-2.md
    • 使用 Gulp 打包 JavaScript 解決循環相依 (Circular dependencies) 問
    • RequireJS进阶:配置文件的学习
    • 使用 Html-Webpack-Plugin 引入 Bundle 檔案
    33

    View full-size slide