前戏乱侃:
话说由于某js引擎得到推广,使js得以脱离浏览器,可在本地很好执行,于是乎NodeJS出现了。js可以开发本地应用了,开发这种应用那得有很多js文件吧,总要分分模块才好管理吧,模块分了之后,模块之间怎么引入呢?java中可以用import,C#中可以用using。嗯,js应用中就用require("path/to/module")吧,总应该为之定个规范吧,于是这种js引入方式就被定义为了CommonJS。后来某人受到启发,既然本地应用中可以这样子引用,web App中可否也使用这样的规范。于是require("path/to/module",function(){})这种web js引入方式得以应用,名曰AMD规范,大概的意思就是,模块加载好后,执行后面的方法。以此同时也出现了另一个模块加载规范CMD。其实AMD和CMD规范都差不多,都是用于web的模块加载器,AMD使用的加载器是RequireJS,CMD使用的加载器是SeaJS,RequireJS老外用得比较多,SeaJS国人用得比较多。再后来出现Grunt,Gulp,Webpack的打包工具。NodeJS是什么,其实他就相当于能让js在命令行执行的工具。以上乱侃over,懂得不多,不对之处,请指正。
正文开始
当我还在兴奋的追随SeaJS时,甚至都还没开始搞清楚时,已经开始有人宣判了SeaJS的死刑。在使用SeaJS开发一段时间后,是的,我赞同了这个观点,特别是在模块的打包方面,我至今还是没有找到一个好的解决方案(可能是我知识不够)。感觉SeaJS只做到了模块的分解,却没有找到处理模块合并很好的解决方案。说白了SeaJS只是一个加载器,并不是一个打包工具,它缺的就是一个打包工具。直到后来看到了webpack,在初步了解了一下他的功能后,我真的欣喜若狂,感觉这就是我苦苦寻求的前端构建解决方案。
想一下开发单页面应用程序时,我们的需求:
- 1、我们希望每个view只加载与之相关的资源,,其他view用到的资源在点击首次进入页面时再加载,从而减少页面资源的请求,提高页面加载速度;
- 2、引入其他插件时,我们希望在首次引入插件时,同时把插件用到css引入到页面中,比如 art dialog 插件,重复引用,不会重新加载;
- 3、css,image,js编译、压缩打包、生成版本号,自动替换,并加入到html或schtml中。
是的,webpack都能满足了以上的需求。除了以上的功能外,它还支持各种模块的引入方式天然的支持CommonJS、AMD、CMD,但EA6需要引入相关的loader。
也就是说在一个模块之中,你可以使用以下的定义方式:
说了这么多,上一个我在用的配置方案。自动化构建,说白了玩的就是配置呀,配置好了就是一劳永逸了。
我的方案是:Asp.net mvc+backbone+bootstrap
Asp.net mvc...不解释了;
backbone:单页面应用路由,前端mvc等;
bootstrap 前端css框架。
配置文件的大概思路:
首先我希望登录和主应用的入口是分开的,因此设置了两个程序入口;但这两个入口都同时需要jQuery,因此我将jQuery单独提取出来;
其次,我给mvc View设置了一个模板:views/home/index_tpl.cshtml。每次修改view时只需要修改模板就可以了,构建完后会生成目标页面views/home/index.cshtml,此时index.cshtml中会自动加入各种入口文件
//index_tpl.cshtml
@{
ViewBag.Title = "后台管理-首页";
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div class="aliyun-console-topbar">
</div>
<div class="container-body sidebar-full">
<div class="sidebar"></div>
<div class="main" id="main">
</div>
</div>
</body>
</html>
//构建完后的index.cshtml,webpack会插入各种入口文件
@{
ViewBag.Title = "后台管理-首页";
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>后台管理 . NOCOQ</title>
<link href="/administration/dist/styles/vendors.90866b.css" rel="stylesheet"><link href="/administration/dist/styles/index.2e18b3.css" rel="stylesheet"></head>
<body>
<div class="aliyun-console-topbar">
</div>
<div class="container-body sidebar-full">
<div class="sidebar"></div>
<div class="main" id="main">
</div>
</div>
<script type="text/javascript" src="/administration/dist/vendors.7cf649.js"></script><script type="text/javascript" src="/administration/dist/index.7cf649.js"></script></body>
</html>
最后,说一个小插曲,使用webpack生成目标view后,运行起来后发现中文居然是乱码,找了好久才发现原来是因为webpack生成的文件编码是utf-8 无 bom(utf-8 without bom)格式,而mvc的.cshtml的格式是utf-8 bom格式。于是找到了webpack-utf8-bom这个plugin才顺利的解决了问题。
其他的信息,备注中都有了。
使用webpack资源的引入、加载和打包,一切都这么自然,我还能不喜欢它吗?
//package.json 好像有点不全,不全就按webpack.config.js中找吧。
{
"name": "src",
"version": "1.0.0",
"description": "000",
"main": "webpack.config.js",
"dependencies": {
"babel-core": "^6.17.0",
"babel-loader": "^6.2.5",
"babel-preset-es2015": "^6.16.0",
"bootstrap": "^3.3.7",
"css-loader": "^0.25.0",
"expose-loader": "^0.7.1",
"html-webpack-plugin": "^2.22.0",
"jquery": "^3.1.1",
"jquery-ui": "^1.12.1",
"loader-utils": "^0.2.16",
"lodash": "^4.16.4",
"style-loader": "^0.13.1",
"wangeditor": "^2.1.22",
"webpack": "^1.13.2"
},
"devDependencies": {
"art-template": "^3.0.3",
"art-template-loader": "^0.1.4",
"backbone": "^1.3.3",
"clean-webpack-plugin": "^0.1.13",
"copy-webpack-plugin": "^3.0.1",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.9.0",
"html-webpack-plugin": "^2.22.0",
"mime": "^1.3.4",
"underscore": "^1.8.3",
"url-loader": "^0.5.7"
},
"scripts": {
"test": ""
},
"author": "",
"license": "ISC"
}
//webpack.config.js
var path = require('path');
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin'); // 自动写入将引用写入html
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin"); // 提取公共模块
var ExtractTextPlugin = require("extract-text-webpack-plugin");// 提取/分离css
var CleanWebpackPlugin = require('clean-webpack-plugin'); // 删除文件
var CopyWebpackPlugin = require('copy-webpack-plugin'); // 拷贝文件
var BomPlugin = require('webpack-utf8-bom');//将文件转成utf-8 bom格式,解决中文乱码的问题
//console.log(require.resolve("bootstrap"));
//定义了一些文件夹的路径
var ROOT_PATH = path.resolve(__dirname);
var CTRL_ROOT_PATH = path.resolve(__dirname, "administration");
var CTRL_SRC_PATH = path.resolve(CTRL_ROOT_PATH, 'dev');
var CTRL_DIST_PATH = path.resolve(CTRL_ROOT_PATH, 'dist');
//console.log(CTRL_ROOT_PATH)
module.exports = {
entry: {
index: path.resolve(CTRL_SRC_PATH, 'index.js'),
login_index: path.resolve(CTRL_SRC_PATH, 'login_index.js'),
vendors: ['jquery', 'datepicker'],
//"jquery-ui": ["jquery-ui/themes/base/core.css", "jquery-ui/themes/base/datepicker.css", "jquery-ui/themes/base/theme.css"],
//"style": [path.resolve(CTRL_SRC_PATH, 'styles/style.css')],
},
output: {
//context: path.resolve(__dirname, 'scripts'),
path: path.resolve(CTRL_DIST_PATH),
publicPath: '/administration/dist/',//当生成的资源文件和站点不在同一地方时需要配置改地址 e.g.:站点在src,资源生成到/src/static/dist,那么publicPath="/static/dist"
filename: "[name].[hash:6].js",
chunkFilename: "[id].chunk.js",
},
plugins: [
//new webpack.ProvidePlugin({$: 'jquery'}),
new ExtractTextPlugin("styles/[name].[contenthash:6].css", { allChunks: false }),
new HtmlWebpackPlugin({
title: '后台管理',
template: path.resolve(CTRL_ROOT_PATH, 'views/home/index_tpl.cshtml'),
filename: path.resolve(CTRL_ROOT_PATH, 'views/home/index.cshtml'),
chunks: ['index', 'vendors'], // 配置要添加的模块
inject: 'body'
}),
new HtmlWebpackPlugin({
title: '登录后台管理',
template: path.resolve(CTRL_ROOT_PATH, 'views/account/login_tpl.cshtml'),
filename: path.resolve(CTRL_ROOT_PATH, 'views/account/login.cshtml'),
chunks: ['login_index', 'vendors'], // 配置要添加的模块
inject: 'body'
}),
new CommonsChunkPlugin('vendors', 'vendors.[hash:6].js'),
new CleanWebpackPlugin(['dist', 'build'], {
root: CTRL_ROOT_PATH,
verbose: true,
dry: false,
//exclude: ["dist/1.chunk.js"]
}),
new BomPlugin(true, /\.(cshtml)$/),//解决cshtml中文乱码的问题
],
module: {
/***解决动态路径警告的问题***/
// require
unknownContextRegExp: /$^/,
unknownContextCritical: false,
// require(expr)
exprContextRegExp: /$^/,
exprContextCritical: false,
// require("prefix" + expr + "surfix")
wrappedContextRegExp: /$^/,
wrappedContextCritical: false,
/***end****/
loaders: [
{ test: require.resolve('jquery'), loader: 'expose?$!expose?jQuery' }, // 将jQuery暴露到全局变量中
{ test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader?' + JSON.stringify({ discardComments: { removeAll: true } })) },
{ test: /\.(png|jpg|gif|woff|woff2|eot|ttf|svg)(\?[a-z0-9]+)?$/, loader: 'url-loader?limit=1000&name=fonts/[name].[hash:6].[ext]' }, // 处理图片url
//{ test: /\.(png|jpg|gif)(\?[a-z0-9]+)?$/, loader: 'url-loader?limit=1000&name=images/[name].[hash:8].[ext]' }, // 处理图片url limit=1000 小于1kb则生成base64
//{ test: /\.css$/, loader: "style!css" },
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel', // 'babel-loader' is also a valid name to reference
query: {
presets: ['es2015']
}
},
{ test: /\.tpl/, loader: 'art-template-loader' },
]
},
resolve: {
alias: {
"datepicker": "jquery-ui/ui/widgets/datepicker",
}
}
};
参考资料:
Webpack https://webpack.github.io/docs/
NodeJS https://nodejs.org/
Grunt http://gruntjs.com/
Gulp http://gulpjs.org/
SeaJS http://seajs.org/docs/
RequireJS http://requirejs.org/