搭建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']
        })
    ]
};

結語

至此,咱們已經將項目環境搭建起來了,能夠開始開發了。如有遺漏、錯誤歡迎指出~(整篇文章跨越了較長時間...拖延症惹的禍?

相關文章
相關標籤/搜索