搭建web项目及配置react开发环境

前言

本文的主要内容是,使用koa搭建一个简单的web服务器,并经过webpack配置reactES6开发环境。javascript

搭建一个项目,首先确定是让代码运行起来,能在浏览器端访问编写的html。其次即引入js、css等一些静态文件。为了方便开发,可能还需引入一些框架、处理器,如React、sass、使用ES6等,那么要让浏览器识别,须要对这些进行转化。本文将对这些环节进行说明。css

搭建node环境

node是什么

Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.

简单说,Node.js即运行在服务端的javascript,它是基于chrome的V8引擎。
目前有许多Node.js的框架,如Express.jskoa.js等。本文将使用koa.js来搭建项目环境。html

Koa is a new web framework designed by the team behind Express, which aims to be a smaller, more expressive, and more robust foundation for web applications and APIs. Through leveraging generators Koa allows you to ditch callbacks and greatly increase error-handling. Koa does not bundle any middleware within core, and provides an elegant suite of methods that make writing servers fast and enjoyable.

再简单介绍下koa,与Express相比,它很是小,经过组合不一样的中间件generator减小回调函数嵌套,同时也极大地改进了错误处理方式,使编程更加优雅。
koa教程可参考http://koajs.com/,本文不作过多介绍。前端

搭建koa环境

代码目录结构以下:java

clipboard.png

app.js——入口文件
client——存放html、css、js文件
views——存放html文件
public——存放静态资源
server/router.js——配置不一样的路由
config——配置不一样环境的变量node

具体下文详细说明~react

建立HTTP服务器

首先固然是先安装koa啦~
前提条件:已安装nodewebpack

$ npm init    //建立模块,输入模块名
$ npm install koa@1.2.4 -S        //安装的是koa1

而后新建一个app.js文件,引入koa,并启动端口服务git

/**********app.js***********/
var koa = require('koa');
var app = koa();    //1.初始化koa

//2.注入中间件(必须是generator function)
//this指向当前用户的请求
app.use(function* () {
    this.body = 'Hello Koa';     //输出到页面的内容
});

var port = 3030;
app.listen(port);    //3.为3030端口绑定koa应用,其建立并返回了一个HTTP服务器
console.log(`项目已启动,正在监听${port}`);

执行这个文件:github

node --harmony app.js    //使用harmony

而后咱们就能够经过localhost:3030去访问了。

clipboard.png

能够在package.json中配置script

/**********package.json***********/
"start": "node --harmony app.js"

这样就能经过npm run start去启动项目了。

路由配置

上一小节,咱们经过this.body向页面输入内容。咱们能够加上路由判断:

var path = require('path');

app.use(function *(){
    if (this.path === '/') {
        this.body = 'Hello Koa';
    }
    if (this.path === '/detail') {
        this.body = 'Hello Detail';
    }
});

显然,这个过于繁琐,没法在项目中使用。所以咱们须要引入路由中间件koa-router,这样就能控制不一样路由下返回的页面。

$ npm install koa-router@5.4.1 -S     //加版本号是由于不一样版本用法不一样

使用server/router.js文件进行配置:

var router = require('koa-router')();

router.get('/', function* () {
    this.body = 'Hello World';
});

router.get('/test', function* () {
    this.body = 'test';
});

module.exports = router.routes();

app.js文件中加上:

var router = require('./server/router');
app.use(router);

访问localhost:3030/test即返回以下:

clipboard.png

固然了,咱们仍是经过this.body向页面输入内容。那么,如何返回相应的html文件呢?下一小节见分晓~

经过路由返回相应的html

基于MVC模式,咱们要将html模板抽离到view中,方便维护。这就须要用到模板引擎了。koa提供了许多模板引擎,如ejs、xtemplate等,本文引入了 koa-swig,由于最终将经过react实现组件化,所以未进行模板引擎对比。
首先引入:

$ npm install koa-swig -S

app.js文件中加上:

app.context.render = render({
    root: path.join(__dirname, './view'),   //连成须要的路径:/koa-test/view/
    cache: false,
    ext: 'html'
});

其中,path__dirname均是node模块提供的。
path是用于处理文件、目录路径的库,path.join()返回结果是把全部参数用\连成路径。
__dirname指的是当前模块的目录名(即app.js的目录名/koa-test)。

而后再改写server/router.js文件:

router.get('/', function* () {
    yield this.render('./index');   //渲染view/index.html
});

如此,咱们访问localhost:3030就能访问咱们保存在view文件夹下的index.html文件了。

引入静态文件

如上所述,咱们已经能访问到相应的html文件了,那下一步即是引入css、js了。那有人会说,这有什么难的,直接在html文件引入不就能够了吗?那咱们来看看传统的引入方法是否生效~~~
首先在相应的public/css文件夹中添加的index.css,加入以下代码:

/*********** public/css/index.css ***********/
body {
    color: red;
}

而后在view/index.html引入,以下:

/*********** view/index.html ***********/
<link rel="stylesheet" href="../public/css/index.css"/>

从新启动应用,发现页面以下,样式并无生效

clipboard.png

是否是以为很奇怪?咱们看看Network请求:

clipboard.png

能够看出,此文件Not found了。
仔细看一看请求的URL,localhost:3000 进入了咱们的服务器,没问题。可是 /public/css/index.css这个路径是个啥?要知道咱们的服务器并无对这个 path 作处理,固然就找不到这个资源了。
最原始的方式,固然是识别这个 path,并返回相应的资源

const fs = require('fs');
app.use(async ctx => {
    if (/\/css\/*/g.test(ctx.request.path)) {
        ctx.response.type = 'text/css';
        ctx.response.body = fs.createReadStream(`./client${ctx.request.path}`);
    }
});

若是每加载一个静态资源都对路径作一次识别,那简直太可怕了...因此,就要用到静态资源路径配置的中间件了。
koa-static-server库是koa提供的 用于配置静态文件的路径。首先引入:

$ npm install koa-static-server -S

app.js文件中加上:

/*********** app.js ***********/
var serve = require('koa-static-server');
app.use(serve({
    rootDir: __dirname + '/public',   //服务的文件(即配置的静态文件路径
    rootPath: '/public'               //被重写的路径(即将用什么路径去访问此文件
}));

再次启动服务,能够发现咱们的页面已经加载入css文件中写的样式啦~

通过这些配置,咱们已经成功搭建了node环境,能够开始开发了~

使用webpack搭建react+ES6环境

目前项目中常常会使用react、ES6开发。因为这些框架、语言浏览器还不支持,咱们须要将其转成浏览器支持的ES5。咱们但愿的是:开发时,搭建好的项目环境就能帮咱们转换。那么就须要如下的工做啦~~

建立React页面

首先,安装React相关依赖:

$ npm install react react-dom -S

client文件中新建index.js文件,用于编写咱们的React组件,内容以下:

/*********** client/index.js ***********/
import React, {Component} from 'react';
import ReactDOM from 'react-dom';

class Test extends Component {
    constructor() {
        super();
    }

    render() {
        return (
            <div className="content">Hello, React</div>
        )
    }
}

ReactDOM.render(<Test />,
    document.getElementById('container')
);

使用webpack进行打包

webpack is a module bundler for modern JavaScript applications. When webpack processes your application, it recursively builds a dependency graph that includes every module your application needs, then packages all of those modules into a small number of bundles - often only one - to be loaded by the browser.

webpack 是一个前端资源加载和打包的工具,能够对CSS、JS、图片、字体等模块进行编译、打包处理。要编译不一样类型的文件,首先须要安装相应的loader加载器,以下:

$ npm install webpack -D
$ npm install babel-loader babel-core babel-preset-react babel-preset-es2015 -D

webpack使用webpack.config.js做为配置文件,是一个标准的commonJS规范的模块,以下:

var path = require('path');

module.exports = {
    entry: './client/index.js',
    output: {
        path: path.join(__dirname, '/public/'),
        filename: 'js/app.js'
    },
    resolve: {
        extensions: ['.js', '.jsx']
    },
    module: {
        loaders: [{
            loader: "babel-loader",
            test: /\.jsx?$/,
            query: {
                presets: ['es2015', 'react']
            }
        }]
    }
};

webpack配置文件的各个参数

entry

entry是入口文件,即须要编译的js文件;能够是字符串、数组或对象。其相对路径是相对于当前文件的。
如上例,咱们须要编译的即client/index.js文件

ouput

output指的是定义输出文件的格式,即编译后的文件。多个entry文件只对应一个output文件。其中有如下经常使用的参数:

path:输出文件的绝对路径。按设定的目录结构,需编译到public/js文件夹中;
filename:编译后的文件名,也能够为'[name].js',即以编译前文件名命名。

The publicPath specifies the public URL address of the output files when referenced in a browser.

publicPath:网站引用文件时访问的路径,当在不一样域上托管一些文件时可使用这个参数。以后咱们会用到。

resolve

resolve:用于修改经常使用模块配置的属性。有许多配置项,以下:

  • extensions: array类型,用于配置文件后缀名,它能够用于自动补全后缀。
  • alias:为模块配置别名
  • root:定义查找模块的路径,必须为绝对路径

举个栗子:

resolve: {
        //从这里开始查找module
        root: '/node_modules/',

        //自动扩展文件后缀, 即require时能够不写后缀, 例如`Hello.jsx`就可使用`import Hello from 'Hello'`;
        extensions: ['.js', '.jsx'],
        
        //为模块配置别名, 方便直接引用
        alias: {
            'test': '/utils/test.js'    //引用此文件, 只需require('test')
        }
    }

module

module提供了不一样加载器loaders处理不一样的模块(JS、Sass、CSS、image等)。如下是关于loaders的定义。

Loaders are transformations that are applied on a resource file of your app. They are functions (running in node.js) that take the source of a resource file as the parameter and return the new source.

简单说,即,使用加载器将浏览器不支持的语言转化为它支持的语言。

loaders是array类型,是loader的集合,其中每一项loader有如下参数:

  • loader:string类型,表示用来加载这种资源的loader,‘-loader'能够省略。可使用!来链接不一样的loader,将从右向左进行加载。
  • test:表示匹配的资源类型,可使用正则表达式。
  • exclude:用于屏蔽不须要被loader处理的文件路径
  • include:array类型,表示须要被loader处理的文件路径

举个栗子:

module: {
        //加载器配置
        loaders: [{
            loader: "babel-loader",     //转换react及ES6语法
            test: /\.jsx?$/,
            query: {
                presets: ['es2015', 'react']
            }
        }, {
            test: /\.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/,     //字体转换
            loader: 'file-loader'
        }]
    }

注意,其中使用的全部的loader都需用npm加载进来。

plugins

能够引用一些插件来知足不一样的需求,例如ExtractTextPlugin用于提取文件等,后文会再提到~

plugins: [
        //引用的插件
]

以上只列举一些经常使用且在此文中用到的参数,webpack还有不少参数,详情见:http://webpack.github.io/docs...

开始编译React文件

根据咱们上上节编写的webpack.config.js文件,就能够进行打包了。在命令行运行:

webpack

执行后发现public/js下多了一个app.js文件,那就是咱们编译后的文件。
views/index.html中加入:

/*********** view/index.html ***********/
<script type="text/javascript" src="/public/js/app.js"></script>

访问localhost: 3030便可以看到:
Alt text

使用Sass处理样式

以前咱们只使用普通css来处理样式,且经过<style>标签引入。对于预编译 css 语言,则需使用其余的loader处理,下面以 sass为例:
首先加载:

npm install style-loader css-loader sass-loader node-sass -D

client文件中加入index.scss,以下:

/*********** client/index.scss ***********/
#container {
    font-size: 20px;
    .content {
        color: blue;
    }
}

client/index.js中引入:

/*********** client/index.js ***********/
require('./index.scss');

webpack.config.js中加入loader:

module: {
    loaders: [{
        loader: "babel-loader",     //转换react及ES6语法
        test: /\.jsx?$/,
        query: {
            presets: ['es2015', 'react']
        }
    }, {
        loader: "style-loader!css-loader!sass-loader",  //从右向左执行
        test: /\.(scss|sass)$/
    }]
}

其中,css-loader,实如今js中经过require的方式引入css
style-loader是将样式插入到页面的style标签中。

从新编译,访问localhost:3030,即可以看到效果了。检查元素,咱们能够看到:

clipboard.png

编译后的css将样式插入到页面的<style>标签中了。

注意,webpack2不支持省略 -loader

提取css文件

上一小节中,咱们是经过将css内嵌在js文件中,并将样式插入<style>标签的方式引入css,但若是css文件较庞大,内嵌在js中会减慢加载速度,所以应当把css生成到单独的文件中。
那么就需引入插件了~~

npm install extract-text-webpack-plugin -D

这个插件是用来分离css的。使用以下:

const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
    module: {
        //加载器配置
        loaders: [{
            loader: "babel-loader",     //转换react及ES6语法
            test: /\.jsx?$/,
            query: {
                presets: ['es2015', 'react']
            }
        }, {
            test: /\.(scss|sass)$/,        //能够在js中直接require相应的scss
            loader: ExtractTextPlugin.extract({
                fallback: "style-loader",
                use: "css-loader!sass-loader"
            })
        }]
    },
    plugins: [
        //将内嵌在js文件中的css抽离
        new ExtractTextPlugin({
            filename: 'css/vendor.css',
            disable: false,
            allChunks: true
        })
    ]
};

filename指的是抽离后的css文件,该路径是相对output中的path的。
从新webpack,能够看到public/css文件下生成编译后的vendor.css文件

自动构建

完成以上的步骤,咱们就能够开始开发了。那么问题来了,每次写完代码都须要从新 webpack构建,且从新刷新网页才能看到效果,实在是又繁琐又无聊。
一种更好的方式是启动一个静态资源服务器,监听代码变化并自动打包构建、刷新页面。webpack官方提供了一个第三方库:

npm install webpack-dev-server -D

package.json中配置:

/**********package.json***********/
"build": "webpack-dev-server --port 4040 --progress --config webpack.config.js"

这句命令的意思是,在localhost: 7070创建一个web服务器;经过--port指定端口号,默认为8080端口;--progress是显示代码的打包进度。

自动建立index.html页面

以前咱们是手动建立views/index.html文件,引入相关的资源文件,而后渲染该页面。
假设咱们项目准备上线了,而开发时引入的资源文件是在热更新服务上的,与上线要使用的资源文件地址是不同的,那每次上线或切到本地开发,都要进行修改?
下面就来介绍的插件HtmlWebpackPlugin,就是为了解决这种问题。

The HtmlWebpackPlugin simplifies creation of HTML files to serve your webpack bundles. This is especially useful for webpack bundles that include a hash in the filename which changes every compilation.

简单点说,就是用来生成html文件的,另外能够帮咱们注入css、js等资源文件。

npm install html-webpack-plugin -D

它包含如下参数:

  • title:用于html的title
  • filename:注入的html的文件名,默认为index.html。注意:此路径相对于output中的path
  • template:模板文件路径,能够根据指定的模板文件生成特定的html文件
  • inject:true | 'head' | 'body' | false,表示是否注入资源到特定的模板及注入的位置。若设置为true或body,javascript资源将被放至body元素底部,'head'则放至head元素中。注意:注入的资源文件路径是相对于publicPath,而非当前路径
  • cache:是否缓存,默认为true,即只在内容变化时才生成新文件
  • favicon:注入html中的favicon的文件名
  • chunks:容许只注入某些模块的资源(当entry中定义了多个入口文件,可针对其中的几项注入)
  • excludeChunks:与chunks相对,即排除注入某些模块的资源

上述只列出一些经常使用且下文将使用的参数,如需了解所有,请参考:https://github.com/jantimon/h...

经过了解的参数,咱们在webpack.config.js中加入这些代码:

/*********** webpack.config.js ***********/
output: {
        path: path.join(__dirname, '/public/'),
        filename: 'js/[name].js',
        publicPath: '/public'
},
plugins: [
        htmlWebpackPlugin({
            filename: 'views/index.html',       //非相对于当前路径, 而是相对于output的path
            template: clientPath + 'views/index.html',  //未指定loader时, 默认使用ejs-loader
            inject: true,
            chunks: ['app']
        })
]

从新打包,能够看到/public路径下生成了views/index.html文件,且分别在头部和底部注入了css、js,以下:

<!--------- public/views/index.html ------------>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>test</title>
<link href="/public/css/vendor.css" rel="stylesheet"></head>
<body>
<div id="container"></div>
<script type="text/javascript" src="/public/js/app.js"></script></body>
</html>

同时,要记得将app.js中返回html的路径进行修改,并从新起服务:

/*********** app.js ***********/
app.context.render = render({
    root: path.join(__dirname, '/public/views'),
    cache: false,
    ext: 'html'
});

可是咱们发现,注入的并非咱们热更新服务上的资源文件。所以咱们得区分开发、生产环境,并注入不一样路径的资源文件。有两种方法,一种是经过区分环境写入不一样的publicPath;另外一种是经过HtmlWebpackPlugin的自定义变量,开发环境由咱们本身注入资源,生产环境才依赖自动注入。
方法一

/*********** webpack.config.js ***********/
//经过写入不一样的publicPath
const dev = process.env.BUILD_ENV === 'dev';

output: {
    path: path.join(__dirname, '/public/'),
    filename: 'js/[name].js',
    publicPath: dev ?  'http://localhost:8080/public/' : '/public' 
}        //区分开发、生产环境

方法二:

/*********** webpack.config.js ***********/
const dev = process.env.BUILD_ENV === 'dev';

plugins: [
    new HtmlWebpackPlugin({
        dev: dev,                //加入自定义变量
        filename: 'views/index.html',
        template: clientPath + 'views/index.html',
        inject: !dev,            //只在生产环境自动注入
        chunks: ['app']
    })
]

client/views/index.html手动注入开发环境所需的资源文件:

<!--------- client/views/index.html ------------>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>test</title>
    <% if(htmlWebpackPlugin.options.dev) { %>
    <link rel="stylesheet" href="http://localhost:8080/public/css/vendor.css" />
    <% } %>
</head>
<body>
<div id="container"></div>

<% if(htmlWebpackPlugin.options.dev) {%>
<script type="text/javascript" src="http://localhost:8080/public/js/app.js"></script>
<% } %>
</body>
</html>

这两种方法,均依赖process.env.BUILD_ENV变量,process.env是node提供的用于返回用户运行环境的。这个变量是在执行webpack时加上的。

//生产环境
BUILD_ENV=production webpack --config webpack.config.js

//开发环境
BUILD_ENV=dev webpack --config webpack.config.js

能够发现,经过区分环境,在生成的public/views/index.html文件中注入的资源文件路径是不一样的,符合咱们的需求。
固然,咱们每次运行时都要敲这么一长串的代码,很容易犯错,那么能够经过在package.json中的script中加上如下代码:

"scripts": {
    "start": "node --harmony app.js",
    "production": "BUILD_ENV=production webpack --config webpack.config.js",
    "dev": "BUILD_ENV=dev webpack --config webpack.config.js",
    "build": "webpack-dev-server --port 8080 --progress --hot --config webpack.config.js"
  }

便可经过npm run start启动服务,npm run build启动热更新服务。
到此,咱们就完成了html文件注入。

代码压缩

能够看到,引入的资源文件没有进行压缩,若是资源文件较大的话,加载时间就会很长。
webpack提供了一个压缩的插件UglifyJsPlugin,使用以下:

/*********** webpack.config.js ***********/
const webpack = require('webpack');

plugins: {
    new webpack.optimize.UglifyJsPlugin({
            comments: false,     //是否在输出时保留注释
            compress: {
                warnings: false      //是否打印warning信息
            }
        })
}

compressboolean|object,为false则不压缩
这是webpack官网提供的较为快速、高效的压缩方法~
最终,webpack文件以下:

/*********** webpack.config.js ***********/
var path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const dev = process.env.BUILD_ENV === 'dev';

const clientPath = path.join(__dirname, '/client/');

module.exports = {
    entry: {
        app: ['./client/index.js']
    },
    output: {
        path: path.join(__dirname, '/public/'),
        filename: 'js/[name].js',
        publicPath: dev ? 'http://localhost:8080/public/' : '/public'
    },
    resolve: {
        //自动扩展文件后缀, 即require时能够不写后缀, 例如`Hello.jsx`就可使用`import Hello from 'Hello'`;
        extensions: ['.js', '.jsx']
    },
    module: {
        //加载器配置
        loaders: [{
            loader: "babel-loader",     //转换react及ES6语法
            test: /\.jsx?$/,
            query: {
                presets: ['es2015', 'react']
            }
        }, {
            test: /\.(scss|sass)$/,        //能够在js中直接require相应的scss
            loader: ExtractTextPlugin.extract({
                fallback: "style-loader",
                use: "css-loader!sass-loader"
            })
        }]
    },
    plugins: [
        //将内嵌在js文件中的css抽离
        new ExtractTextPlugin({
            filename: 'css/vendor.css',
            disable: false,
            allChunks: true
        }),
        new webpack.optimize.UglifyJsPlugin({
            comments: false,
            compress: {
                warnings: true
            }
        }),
        new HtmlWebpackPlugin({
            filename: 'views/index.html',       //非相对于当前路径, 而是相对于output的path
            template: clientPath + 'views/index.html',  //未指定loader时, 默认使用ejs-loader
            inject: true,
            chunks: ['app']
        })
    ]
};

结语

至此,咱们已经将项目环境搭建起来了,能够开始开发了。如有遗漏、错误欢迎指出~(整篇文章跨越了较长时间...拖延症惹的祸?

相关文章
相关标签/搜索