引言

本文从一个很小的前端工程说起,慢慢推导出我目前多个项目前端实践的工程结构。

为Web前端项目建工程

Web工程需要建工程吗?不是建个目录就可以开搞的吗?你一定会这么问。但事实上面对越来越复杂的前端页面逻辑,真的有必要好好组织一下前端的代码结构。同时前端代码的依赖管理、编译方式真的需要考量一下。

mkdir -p demo1
cd demo1
npm install # 回答一系列回答后,一个npm工程就建好了

创建前端源代码目录及编译后产物目录

从Java带来的习惯,我是不习惯将源代码与编译后的产物放在一起的,还是分开一点好。

mkdir web-src #前端源代码目录
mkdir public #前端源代码编译后产物目录

来一个Hello World

在web-src目录下写一个JS版的hello world

web-src/js/demo1.js

console.log('Hello World!');

写编译脚本

在demo1目录中执行命令安装webpack

npm install webpack --global #--global是安装到系统全局
npm install webpack --save-dev #--save-dev是安装到node_modules目录下,并修改package.json文件,添加此开发依赖

在demo1目录建一个webpack.config.js文件,在这里配置如何使用webpack编译。

webpack.config.js

var webpack = require("webpack");

var output_options = {
    path: 'public',
    publicPath: '/demo1/',
    filename: 'js/[name].js',
    hotUpdateMainFilename: 'hot-update/[hash].hot-update.json',
    chunkFilename: 'js/chunks/[name].js',
    hotUpdateChunkFilename: 'hot-update/chunks/[id].[hash].hot-update.js',
};

var entries = {
    demo1 : __dirname + '/web-src/js/demo1.js'
};

var webpackConfig = {
    entry: entries,
    output: output_options
};

module.exports = webpackConfig;

这时在demo1目录下执行webpack命令,就会在public/js目录下生成编译后的js文件

webpack

你打开public/js/demo1.js一看,肯定吓一跳,就一个简单的hello world,怎么就编出这么大的文件,这个是脱裤子放屁。如果这个系列你继续看下去,你会发现这一些也是值得的。

生成html页面

光编出js文件,如果没法在浏览器里查看运行效果也是白搭,我不建议自己手工写html页面来引用生成的js文件,还是让webpack来做吧。

安装webpack生成html的插件

npm install html-webpack-plugin --save-dev

在web-src/html目录下创建html的模板文件

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title><%= htmlWebpackPlugin.options.title || 'Webpack App'%></title>
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <meta name="renderer" content="webkit">
    <% for (var css in htmlWebpackPlugin.files.css) { %>
    <link href="<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet">
    <% } %>
</head>
<body class="body-cls">
    <% for (var chunk in htmlWebpackPlugin.files.chunks) { %> <script src="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script> <% } %>
</body>
</html>

在webpack.config.js文件加入几行

...

var HtmlWebpackPlugin = require('html-webpack-plugin');

var createHtmlDef = function(opts){
    return new HtmlWebpackPlugin({
        title: 'demo1',
        filename: opts.path,
        template: 'web-src/html/base.html',
        chunks: opts.chunks,
        hash: true,
        inject: false
    });
};

var plugins_options = [
    createHtmlDef({path: 'demo1.html', chunks: ['demo1']})
];

var webpackConfig = {
    ...
    plugins: plugins_options,
    ...
};

...

然后在demo1目录下执行webpack命令,即完成编译

webpack

部署到nginx里

我本机安装了nginx,因此就直接把这个小demo部署到nginx里吧

ln -sf demo1/public /usr/local/var/www/demo1

最后在浏览器里访问http://127.0.0.1/demo1/demo1.html,打开chrome的控制台,应该就可以看到Hello World了。这么费劲才搞了个hello world, 确实很无聊,下篇我们在页面上做点好玩的。本篇源代码地址

webpack的一些概念

本篇里出现了不少webpack的概念,这里简单介绍一下,详细的可以参考官方文档

webpack里有三个概念:入口文件(entry),分块(chunk),模块(module)。

module指各种资源文件,如js、css、图片、svg、scss、less等等,一切资源皆被当做模块。

chunk:包含一个或者多个资源,必须可以被其它资源用require依赖。

entry:入口,也是包含了一个或者多个资源,它是一种特殊的chunk,不是一定得能被其它资源依赖,通常是使用html标签直接引入到页面里的。

比如本篇中项目执行webpack命令的输出如下

Hash: 7bd5b3403fe5f918821c
Version: webpack 1.13.0
Time: 528ms
      Asset       Size  Chunks             Chunk Names
js/demo1.js    1.42 kB       0  [emitted]  demo1
 demo1.html  318 bytes          [emitted]
   [0] ./web-src/js/demo1.js 29 bytes {0} [built]
Child html-webpack-plugin for "demo1.html":
        + 3 hidden modules

这里可以看到entry里配置的名称为demo1的entry生成了一个同名的chunk, 该chunk的编号为0,该chunk最后编译生成了js/demo1.js。而通过HtmlWebpackPlugin插件生成了demo1.html,该插件的配置里写明了生成的html页面需要引入名称为demo1的chunk,因此最后生成的html页面里就用script标签引入了js/demo1.js,并且由于hash: true,引入的url后面还加了hash,以避免浏览器缓存js。

事实上除了定义entry的方式间接声明一个chunk外,还有另一种方法,这个后面会遇到,到时再说吧。