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等,可是目前已经能够知足初步需求了。接下来再作进一步的优化。

# 栗子
栗子

打个广告

我的站点

相关文章
相关标签/搜索