Webpack in Visual Studio

前言

用webpack來構建純前端項目已經司空見慣了,可是,其實並無不少公司真正的實現先後端分離,因此使用asp.net mvc開發仍是很廣泛的。
以前使用.net mvc + vue + elementui 開發感受駕輕就熟,效率高了不少,可是遇到一個問題:在使用ES6語法時,會遇到兼容性問題。
剛開始的時候採用了traceur-compiler來進行實時編譯,後來以爲這個總不是best practise,因而開始嘗試使用自動構建的方式來實現。javascript

Gulp

最初的打算是使用Gulp來進行自動構建,可是發現使用Gulp有如下幾個問題:css

  1. 須要依賴的庫太多
  2. 想實現諸如增長基於內容的hash值做爲文件名稱和實現"livereload"都比較麻煩

Webpack

綜合考慮下仍是決定直接使用webpack來作:html

  1. 直接能夠輸出hashchunkname
  2. 簡單的在命令行加一個-w就能夠實現"livereload"

基本應用

引入webpack的最基本目的是爲了編譯ES6的代碼,所以只須要引入babel就能夠了。前端

  • 項目文件的根目錄執行npm init來初始化package.json文件(一路回車便可)
  • 在當前項目目錄下執行npm install --save-dev webpack babel-core babel-loader babel-preset-2015,這樣最基礎的插件就安裝好了
  • 接下來還要安裝兩個插件,第一個是clean-webpack-plugin,用於清除目錄,第二個是assets-webpack-plugin,這個用以生成將js文件和帶chunkhash值的文件映射關係的文件,最後還有一個用以裝逼的插件webpack-notifier,這個是用來通知webpack執行狀態的 (注意:這裏若是須要使用babel-polyfill也能夠引入)

這時沒什麼好多說的,都是webpack的基本使用,設置entry,module裏面設置使用babel-loader來加載".js"文件
同時在執行webpack腳本時增長'-w'參數,就能夠實現修改js文件後自動編譯了vue

長效緩存

爲了實現長效緩存,戳這裏,還須要使用'[name].[chunkhash].js'來給編譯後的js名添加hash值,可是這樣就產生了不少新的問題java

  • 由於hash值是根據內容動態變化的,那麼開發的時候怎麼知道該引用什麼名字呢?
  • 一樣由於是動態變化的,因此不能把這些文件包含在項目中,所以致使發佈的時候,這些文件並不會複製到發佈目錄
    解決辦法以下:
  1. 使用上面提到的assets-webpack-plugin插件,能夠在構建時,生成filename與filename-chunkhash的映射關係。並將這個映射關係保存下來,這樣就能夠讀取這個文件,以相似反射的方式加載
  2. 爲了這個問題還去研究了一下msbuild,最終經過本身手動複製的方式實現

自動反射業務js文件

考慮到若是須要每次新增一個js文件,都要去修改webpack.config.js的入口(entry)未免有點麻煩,因而考慮也用相似反射的思路,來讀取用來存放業務js代碼的文件夾,把每一個".js"都做爲入口文件的一個對象:node

var readFile = function (path) {
    var fs = require('fs');
    var files = fs.readdirSync('./' + path);
    var jsFiles = files.filter((f) => {
        return f.endsWith('.js');
    });

    var ret = {};
    jsFiles.forEach(function (item) {
        var temp = item.substring(item, item.indexOf(".js"));
        ret[temp] = "./" + SOURCE_DIRECTORY + "/" + item;

    });
    return ret;
};

最終生成出來的樣子以下所示:webpack

// entry: {
//   wp:[ './app/webpack.js'] // 獨立的業務文件

// }, // 入口文件定義

舉個栗子

最終的webpack.config.js文件應該長這樣:git

'use strict';

const AssetsPlugin = require('assets-webpack-plugin'); // 將每一個資源文件的原生filename和chunkhashname一一對應起來的插件
const CleanPlugin = require('clean-webpack-plugin'); // 清空目錄的插件
const path = require('path'); // 顧名思義,路徑相關操做的插件
const webpack = require('webpack'); // 主角,webpack
const pkg = require('./package'); // package.json
var WebpackNotifierPlugin = require('webpack-notifier');

const SCRIPTS_ROOT = 'Content/Scripts/';
const SOURCE_DIRECTORY = 'app'; // 業務文件名
const BUILD_DIRECTORY = 'build'; // 構建目錄名
const BUILD_DROP_PATH = path.join(__dirname, SCRIPTS_ROOT, BUILD_DIRECTORY);
//const BUILD_DROP_PATH = path.resolve(__dirname, BUILD_DIRECTORY); // 構建全路徑
const WEB_ROOT = path.join(__dirname, SCRIPTS_ROOT); // 當前上下文


const CHUNK_FILE_NAME = '[name].[chunkhash].js';

var readFile = function (path) {
    var fs = require('fs');
    var files = fs.readdirSync('./' + path);
    var jsFiles = files.filter((f) => {
        return f.endsWith('.js');
    });

    var ret = {};
    jsFiles.forEach(function (item) {
        var temp = item.substring(item, item.indexOf(".js"));
        ret[temp] = "./" + SOURCE_DIRECTORY + "/" + item;

    });
    return ret;
};


var webpackEntry = readFile(path.join(SCRIPTS_ROOT, SOURCE_DIRECTORY));

console.log(webpackEntry);

const config = {

    context: WEB_ROOT, // 聲明上下文

    // entry: {
    //   wp:[ './app/webpack.js'] // 獨立的業務文件

    // }, // 入口文件定義
    entry: webpackEntry,

    module: {
        loaders: [
        {
            test: /\.js$/,
            loader: 'babel-loader',
            query: {
                presets: ['es2015']
            },
            exclude: /node_modules/
        },
        {
            test: /(\.css$)/,
            loaders: ['style-loader', 'css-loader', 'postcss-loader']
        },
       {
           test: /\.(png|woff|woff2|eot|ttf|svg)$/,
           loader: 'url-loader?limit=100000'
       }
        ]
    }, // 使用babel編譯js


    output: {
        path: BUILD_DROP_PATH,
        filename: CHUNK_FILE_NAME,
        chunkFilename: CHUNK_FILE_NAME
    }, // 輸出

    plugins: [
    new AssetsPlugin({
        filename: 'webpack.assets.json',
        path: BUILD_DROP_PATH,
        prettyPrint: true
    }), // 將filename 和 filename.chunkhash對應起來

    new CleanPlugin(BUILD_DROP_PATH), // 構建前先清空文件夾

    new webpack.optimize.UglifyJsPlugin({
        compress: {
            warnings: false
        },
        output: {
            comments: false
        }
    }), // 壓縮混淆

    new WebpackNotifierPlugin(), // 通知結果
    ],
};

module.exports = config;

到目前爲止,webpack能作的事就結束了,接下來.net怎麼配合呢?github

cshtml頁面引用

前面提到了,編譯出來的js文件名是含有chunkhash值的,因而最理想的使用方法就是在開發過程當中感受不到變化,仍是像以前同樣,只須要寫"filename"就能夠引入含有"chunkhash值"的編譯後的js文件。
作法以下:
首先,擴展一下UrlHelper:

public static class WebpackExtension
    {
        public static string GetResourceUrlHashChunk(this UrlHelper helper, string fileName)
        {
            JObject webpackAssetsJson = null;
            var applicationBasePath = System.AppDomain.CurrentDomain.BaseDirectory;
            string packageJsonFilePath = $"{applicationBasePath}\\{"package.json"}";

            using (StreamReader packageJsonFile = File.OpenText(packageJsonFilePath))
            {
                using (JsonTextReader packageJsonReader = new JsonTextReader(packageJsonFile))
                {
                    JObject packageJson = (JObject)JToken.ReadFrom(packageJsonReader);
                    JObject webpackConfigJson = (JObject)packageJson["customConfig"]["webpackConfig"];
                    string webpackAssetsFileName = webpackConfigJson["assetsFileName"].Value<string>();
                    string webpackBuildDirectory = webpackConfigJson["buildDirectory"].Value<string>();
                    string webpackAssetsFilePath = $"{applicationBasePath}\\{webpackBuildDirectory}\\{webpackAssetsFileName}";

                    using (StreamReader webpackAssetsFile = File.OpenText(webpackAssetsFilePath))
                    {
                        using (JsonTextReader webpackAssetsReader = new JsonTextReader(webpackAssetsFile))
                        {
                            webpackAssetsJson = (JObject)JToken.ReadFrom(webpackAssetsReader);
                        }
                    }
                    return "~/" + webpackBuildDirectory + webpackAssetsJson.SelectToken(fileName).Value<string>("js");

                }
            }
        }
    }

這段代碼作的事情就是把AssetsPlugin這個插件生成的包含映射關係的文件讀出來,而後根據文件名找到真實路徑
因而在前臺(cshtml)頁面中使用時,能夠直接使用如下方法:

<script type="text/javascript" src="@Url.Content(@Url.GetResourceUrlHashChunk("webpack"))"></script>

注:webpack是我這個js的名稱

發佈問題

事情到這裏尚未結束,在發佈時又發生了情況,那就是這些編譯後的文件因爲是動態生成的,沒法包入項目文件,所以也就不會複製的發佈目錄中,解決方法以下:

在項目根目錄下的Properties\PublishProfiles文件夾中,會保存發佈的配置文件,因爲visual studio是使用MSBuild進行編譯發佈的,所以在對MSbuild略加研究以後,pubxml的內容以下:
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <WebPublishMethod>FileSystem</WebPublishMethod> <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration> <LastUsedPlatform>Any CPU</LastUsedPlatform> <SiteUrlToLaunchAfterPublish /> <LaunchSiteAfterPublish>True</LaunchSiteAfterPublish> <ExcludeApp_Data>False</ExcludeApp_Data> <publishUrl>F:\WebDemo\webpack</publishUrl> <jsPublishUrl>F:\WebDemo\webpack\Content\Scripts\build</jsPublishUrl> <!--這裏務必設爲false--> <DeleteExistingFiles>False</DeleteExistingFiles> <PrecompileBeforePublish>True</PrecompileBeforePublish> <EnableUpdateable>True</EnableUpdateable> <DebugSymbols>False</DebugSymbols> <WDPMergeOption>DonotMerge</WDPMergeOption> </PropertyGroup> <ItemGroup> <!--定義須要copy到發佈目錄的文件 定義發佈前須要先刪除的文件夾--> <WebFiles Include="$(ProjectDir)\Content\Scripts\build\*.*" /> <OutputFiles Include="$(publishUrl)\**\*.*" /> </ItemGroup> <!--先刪除以前老的發佈文件--> <Target Name="RemoveDirectories" AfterTargets="AfterBuild"> <Delete Files="@(OutputFiles)" /> </Target> <!--將帶hashchunk的壓縮過的js文件copy到發佈目錄--> <Target Name="moveChunkhashJs" AfterTargets="RemoveDirectories"> <MakeDir Directories="$(jsPublishUrl)" Condition="!Exists('$(jsPublishUrl)')" /> <Copy SourceFiles="@(WebFiles)" DestinationFolder="$(jsPublishUrl)"> </Copy> </Target> </Project>

vs2015 webpack task runner 插件

在vs2015裏還能夠直接安裝webpack task runner 插件來使用
如圖所示,還能夠將webpack腳本和生成事件綁定起來了。
最後還有一個小坑:戳這裏
若是你在命令行運行腳本沒問題,可是使用webpack task runner 出了問題,頗有可能就是這個緣由,配置"外部Web公交"時,的位置順序不對。
plugin1
plugin2
plugin3

# 小結

其實還有不少能夠優化的地方,好比第三方庫,css等,可是目前已經能夠知足初步需求了。接下來再作進一步的優化。

# 栗子
栗子

打個廣告

我的站點

相關文章
相關標籤/搜索