Git Product home page Git Product logo

shushu2013.github.io's Introduction

Hexo brisklife 主题博客

学习、记录、成长

博客二维码

shushu2013.github.io's People

Contributors

shushu2013 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

shushu2013.github.io's Issues

Webpack 3.x 学习-第一天

学习资料

深入浅出 Webpack

Webpack3.X版 成神之路 全网首发 (共24集)

这个是跟着上面的资料学习 Webpack 3.X 整理记录下来的学习笔记

Webpack 学习第一步

1、初始化项目并安装 Webpack

系统要求:

    NodeJS 版本 5.0.0 以上
    npm (国内把 cnpm 也安装上)
    编辑器推荐使用 VSCode

新建一个 Web 项目,并初始化

mkdir webpack-demo
cd webpack-demo
npm init    // 会生成一个 package.json 文件,包含项目的相关信息

安装 Webpack 到本地项目(之后所有命令都是在新建的 webpack-demo 目录下进行)

cd webpack-demo

// 国内使用 cnpm 下载
cnpm install --dev-save webpack     // 命令简写:cnpm i -D webpack

// 安装指定版本
cnpm i -D webpack@<version>

// 卸载
npm uninstall -D webpack

2、运行本地安装的 Webpack

官方推荐的 Webpack 配置文件为 webpack.config.js,并且放在项目根目录下

cd webpack-demo
touch webpack.config.js // linux 命令

// windows 下有 cmd 和 powershell 终端,我的 VSCode 里用的是 powershell 终端
copy nul > test.js  // cmd 下创建 test.js 文件
del test.js         // cmd 下删除 test.js 文件

new-item test.js -type file // powershell 下创建 test.js 文件
// 简写:new-item test.js -t f 
rm test.js                  // powershell 下删除 test.js 文件
  • 在项目根目录下运行 ./node_modules/.bin/webpack -v 查看 Webpack 版本

  • npm script 里定义 Webpack 相关任务(会优先使用本项目下安装的 Webpack),定义在项目的 package.json 文件中的 scripts 项中

"scripts": {
    "start": "webpack --config webpack.config.js"
}

之后可以执行: npm run start 来运行 start 这个任务

3、初始项目结构规范

一般我们开发环境的源码放在项目根目录下的 src 目录下,经过 Webpack 构建后生成的生产环境代码放在 dist 目录下

现在,在 src 目录下创建 JS 执行入口文件 index.js,在 dist 目录下创建页面入口文件 index.html

dist 目录下的页面入口文件 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>learn webpack</title>
</head>
<body>
    <div id="app"></div>
    
    <!--导入 Webpack 输出的 JavaScript 文件-->
    <script src="./bundle.js"></script>
</body>
</html>

src 目录下的 JS 执行入口文件 index.js

function show(content) {
  window.document.getElementById('app').innerText = 'Hello,' + content;
}
show('Webpack');

webpack.config.js 基础配置

const path = require('path');

module.exports = {
    // 入口文件的配置项
    entry: {
        index: './src/index.js'
    },
    // 出口文件的配置项
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    // 模块:例如转换 es6, less, 压缩, 图片转换等...
    module: {},
    // 插件,用于生产模板和各项功能
    plugins: [],
    // 配置 webpack 开发本地服务器功能
    devServer: {}
}

此时项目目录结构如下:

|-- webpack-demo    
        |-- dist
        |     |__ index.html
        |-- node_modules
        |-- src
        |     |__ index.js
        |__ package.json
        |__ webpack.config.js

4、初次运行 Webpack 构建命令

之前已经在 package.json 文件中定义了 start 任务

"scripts": {
    "start": "webpack --config webpack.config.js"
}

此时在终端中执行 npm run start 命令来执行相应的 Webpack 构建命令,你会发现在 dist 目录下生成了 bundle.js 文件

在浏览器中打开 dist 目录中的 index.html,就会显示 Hello,Webpack

Webpack 学习第二步

1、重构项目

1.1 项目需求:
  • dist 目录下的 index.html 能根据指定的 html 模板 动态生成
  • index.html 能动态加载 Webpack 打包后生成的多个 js 文件
  • 对生成的 index.html 进行压缩(如:去除注释、空格等)
  • 如果某个文件内容改动,则 Webpack 打包后生成的文件名要不一样,防止浏览器缓存旧的文件

前三个需求可以通过使用 Webpack 插件 html-webpack-plugin 实现

1.2 参考资料:

html-webpack-plugin GitHub 地址
html-webpack-plugin 插件用法
深入浅出webpack教程系列4-插件使用之html-webpack-plugin配置(上)

html-webpack-plugin 内部集成了 html-minifier, 这个压缩选项同 html-minifier 的压缩选项相同
参考:html-minifier GitHub 地址

1.3 先重构我们的项目结构如下:
|-- webpack-demo    
        |-- node_modules
        |-- src
        |     |-- js
        |     |   |__ index.js
        |     |
        |     |__ index.html
        |__ package.json
        |__ webpack.config.js

src 目录下的 index.htmlhtml-webpack-plugin 插件要用到的模板文件,内容如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <!-- 获取 html-webpack-plugin 插件配置项中的 title 信息 -->
    <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
    <div id="app"></div>
</body>
</html>
1.4 安装 html-webpack-plugin 插件
cnpm i -D html-webpack-plugin
1.5 在 webpack.config.js 中引入并配置 html-webpack-plugin 插件
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    // 入口文件的配置项
    entry: {
        index: './src/js/index.js'
    },
    // 出口文件的配置项
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'js/bundle.js'
    },
    // 模块:例如转换 es6, less, 图片转换等...
    module: {},
    // 插件,用于生产模板等各项功能
    plugins: [
        new HtmlWebpackPlugin({
            title: 'learn webpack',         // 标题
            template: './src/index.html',   // html 模板
            filename: 'index.html',         // 生成的 HTML 名称
            inject: 'true',                 // 引入生成的 js 文件,false 则不引入
            minify: {
                removeComments : true,      //去掉注释
                collapseWhitespace : true   //去掉空行
            }
        })
    ],
    // 配置 webpack 开发本地服务器功能
    devServer: {}
}

执行 npm run start 后,会生成 dist 目录,内容如下:

|-- webpack-demo    
        |-- dist
              |-- js
              |   |__ bundle.js
              |
              |__ index.html
1.6 在 webpack.config.js 中配置出口文件名
// 出口文件的配置项
output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/bundle.js'
}

其中 filename 支持 4 种动态名称:id, name, hash, chunkhash

// 使用入口名字
filename: '[name].bundle.js'

// 使用内部块 id
filename: '[id].bundle.js'

// 对每次构建使用唯一的 hash 值,所有输出的文件都是用的这个 hash 值
// 只要有任何一个文件内容改变了,hash 值就会变
filename: '[name].[hash].bundle.js'

// 该 hash 值是基于文件自身的内容改变的,每个文件的 hash 值根据自己的内容变化
filename: '[name].[chunkhash].bundle.js'

参考资料:

webpack 关于 Output 的文档
Webpack中hash与chunkhash的区别,以及js与css的hash指纹解耦方案

现在我们将 webpack.config.js 中出口文件的配置项改为

// 出口文件的配置项
output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/[name]-[chunkhash:8].js'
}

[chunkhash:8] 表示生成 8 位 hash 值

1.7 清理 dist 文件夹

以上需求都解决,但是当改变了 index.js 文件的内容时,生成了新的带 chunkhash 的文件,原先生成的旧文件并没有被删除,于是在打包前,需要清理掉 dist 文件夹,使用 clean-webpack-plugin 插件即可

参考资料

Webpack 官方文档
clean-webpack-plugin GitHub 地址

安装 clean-webpack-plugin 插件

cnpm i -D clean-webpack-plugin

webpack.config.js 中引入并配置 clean-webpack-plugin

const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {

    // ...省略其他配置
    
    plugins: [
        new CleanWebpackPlugin(['dist'])   //构建前,删除 dist 目录
    ]
}

clean-webpack-plugin 的一些配置选项

// 应该被清除的目录或文件
let pathsToClean = [
  'dist',         // 删除 'dist' 目录
  'build/*.*',    // 删除 'build' 目录下的所有文件
  'web/*.js'      // 删除 'web' 目录下所有的 js 文件
]

// 相关选项
let cleanOptions = {
  // 设置绝对路径,默认为项目根目录
  root: __dirname,

  // 是否在控制台显示清除信息
  verbose: true,
  
  // Use boolean "true" to test/emulate delete. (will not remove files).
  // Default: false - remove files
  dry: false,           

  // If true, remove files on recompile. 
  // Default: false
  watch: false,

  // Instead of removing whole path recursively,
  // remove all path's content with exclusion of provided immediate children.
  // Good for not removing shared files from build directories.
  exclude: [ 'files', 'to', 'ignore' ],

  // allow the plugin to clean folders outside of the webpack root.
  // Default: false - don't allow clean folder outside of the webpack root
  allowExternal: false
}

new CleanWebpackPlugin(pathsToClean, cleanOptions)

理解 BFC ( Block Formatting Contexts ) 块级格式化上下文

参考

详说 Block Formatting Contexts (块级格式化上下文)

理解 CSS 中 BFC

1、概念

块级格式化上下文 (block formatting context) 是页面上的一个独立的渲染区域,容器里面的子元素不会在布局上影响到外面的元素。

创建一个块级格式化上下文:
1、float 不为 none
2、position 的值为 absolutefixed
3、 display 为以下其中之一的值:inline-blocktable-celltable-captionflexinline-flex
4、overflowvisible 以外的值 (hidden, auto, scroll)

在 CSS3 中,BFC 叫做 Flow Root,并增加了一些触发条件:

display 的值为 table-caption。
position 为 fixed,其实 fixed 是 absolute 的一个子类,因此在 CSS2.1 中使用这个值也会触发 BFC,只是在 CSS3 中更加明确了这一点。

2、BFC 特性

2.1 阻止垂直方向上的外边距叠加

垂直外边距叠加规则

仅当两个块级元素相邻并且在同一个BFC上下文时,它们在垂直方向之间的外边距才会叠加(实际外边距以最大的那个外边距为准)。

<style type="text/css">
    * {
        margin: 0;
        padding: 0;
    }
    .l-wrap {
        margin: 0 auto;
        width: 800px;
        background: green;  
    }
    p {
        margin: 10px 0;
        background: yellow;
    }
</style>

<div class="l-wrap">
    <p>some text 1</p>
    <p>some text 2</p>
    <p>some text 3</p>
</div>

以上三个 p 元素中,中间的 p 元素与相邻的 p 元素在垂直外边距上产生了叠加,因此相邻边距仍为 10px,而不会是 20px。

阻止垂直外边距叠加

当两个块在不同的 BFC 上下文时,垂直外边距不会叠加。

修改上面的例子,使中间的 p 元素属于新的 BFC 上下文,这时垂直相邻边距变为 10px + 10px = 20px

<style type="text/css">
    * {
        margin: 0;
        padding: 0;
    }
    .l-wrap {
        margin: 0 auto;
        width: 800px;
        background: green;  
    }
    .l-bfc {
        overflow: hidden;
    }
    p {
        margin: 10px 0;
        background: yellow;
    }
</style>

<div class="l-wrap">
    <p>some text 1</p>
    <div class="l-bfc">
        <!-- 如果直接设置 p 产生 BFC,
        设置 inline-block、table-caption、inline-flex 有效,其他无效-->
        <p>some text 2</p>
    </div>
    <p>some text 3</p>
</div>

2.2 包含浮动元素

清除浮动时,有一种方法是设置浮动元素父元素 overflow 属性为 hidden 或 auto 。

这正是利用了 overflow: hidden/auto 触发浮动元素的父元素的 BFC 特性,来包含浮动元素。

<style type="text/css">
    * {
        margin: 0;
        padding: 0;
    }
    .l-wrap {
        margin: 0 auto;
        width: 800px;
        background: green;
    }
    .l-bfc {
        overflow: hidden;
    }
    .o-col {
        float: left;
        width: 31.33%;
        margin: 0 1%;
        background: blue;
    }
</style>
<div class="l-wrap l-bfc">
    <div class="o-col">col 1</div>
    <div class="o-col">col 2</div>
    <div class="o-col">col 3</div>
</div>

2.3 阻止元素被浮动元素覆盖

例子:

<style type="text/css">
    * {
        margin: 0;
        padding: 0;
    }
    .l-wrap {
        margin: 0 auto;
        width: 800px;
        background: green;
    }
    .l-bfc {
        overflow: hidden;
    }
    .floated {
        float: left;
        height: 80px;
        background: red;
        color: white;
        opacity: .6;
    }
    p {
        background: black;
        color: white;
    }
</style>

<div class="l-wrap l-bfc"> 
    <div class="floated">Floated div</div> 
    <p class="l-bfc" style="width: 800px">
        Quae hic ut ab perferendis sit quod architecto,dolor debitis quam rem provident aspernatur tempora expedita.
    </p> 
</div>

可以看到类似如下,浮动元素覆盖了 p 元素上:

在图中整个黑色区域为 p 元素,p 元素的 line boxes(文本行)进行了移位。此处 line boxes 的水平收缩为浮动元素提供了空间。
随着文字的增加,因为 line boxes 不再需要移位,文字最终将会环绕在浮动元素的下方。

给 p 元素创建一个新的 BFC,那么 p 就不会被浮动元素覆盖

<div class="l-wrap l-bfc"> 
    <div class="floated">Floated div</div> 
    <p class="l-bfc" style="width: 800px">
        Quae hic ut ab perferendis sit quod architecto,dolor debitis quam rem provident aspernatur tempora expedita.
    </p>  
</div>

如果父元素宽度不足于容纳 浮动元素和 p 元素,p元素会自动下沉到下一行显示。

<div class="l-wrap l-bfc"> 
    <div class="floated">Floated div</div> 
    <p class="l-bfc" style="width: 800px">
        Quae hic ut ab perferendis sit quod architecto,dolor debitis quam rem provident aspernatur tempora expedita.
    </p> 
</div>

gulp 学习入门

gulp 学习

gulp 的基本安装和使用

参考: Gulp 中文网入门指南

插件

1、gulp-less: 编译 less
2、gulp-ejs: 编译 ejs
3、gulp-data: 获取数据
4、gulp-rename: 文件重命名
5、gulp-clean-css: 压缩 css
6、gulp-connect: 热更新
7、gulp-plumber: 错误处理

1、实现编译 less

1.1 安装 gulp-less

$ cnpm install gulp-less --save-dev

1.2 编写 task

var gulp = require('gulp');
var less = require('gulp-less');

// less 处理
gulp.task('less', function() {
    return gulp.src('src/less/*.less')
        .pipe(less())
        .pipe(gulp.dest('dist/css'));
});

1.3 运行 task

$ gulp less

2、实现编译 ejs 模版

从 JSON 文件中获取数据 + ejs 模版 => 编译为 html

2.1 安装 gulp-ejsgulp-data

$ cnpm install gulp-ejs gulp-data --save-dev

2.2 编写 task

var gulp = require('gulp');
var ejs = require('gulp-ejs');
var data = require('gulp-data');

var path = require('path');
var fs = require('fs');

// getJsonData
var getJsonData = function(file) {
    // G:\workspace\glup-test\src\index.ejs
    // => src/temple/index.json
    var jsonFile = 'src/temple/' + path.basename(file.path, '.ejs') + '.json';
    var data = JSON.parse(fs.readFileSync(jsonFile));
    return data;
}

// ejs 处理
gulp.task('ejs', function() {
    return gulp.src('src/*.ejs')
        .pipe(data(getJsonData))
        .pipe(ejs({},{},{ext: '.html'}))
        .pipe(gulp.dest('./dist'));
});

2.3 运行 task

$ gulp ejs

3、实现文件监听

gulp.watch 会监听文件的修改,一旦有变化就会自动运行相应的任务。

3.1 设置监听任务

var gulp = require('gulp');
var less = require('gulp-less');
var ejs = require('gulp-ejs');
var data = require('gulp-data');

var path = require('path');
var fs = require('fs');

var srcPath = {
    less: 'src/less/*.less',
    ejs: 'src/*.ejs',
    json: 'src/temple/*.json'
};

var buildPath = {
    css: 'dist/css',
    html: 'dist'
};


// 默认执行的任务
gulp.task('default', ['less', 'ejs', 'watch'], function() {
    console.log('default task ok!');
})

// less 处理
gulp.task('less', function() {
    return gulp.src(srcPath.less)
        .pipe(less())
        .pipe(gulp.dest(buildPath.css))
        .pipe(connect.reload());
});

// getJsonData
var getJsonData = function(file) {
    // G:\workspace\glup-test\src\index.ejs
    // => src/temple/index.json
    var jsonFile = 'src/temple/' + path.basename(file.path, '.ejs') + '.json';
    var data = JSON.parse(fs.readFileSync(jsonFile));
    return data;
}
// ejs 处理
gulp.task('ejs', function() {
    return gulp.src(srcPath.ejs)
        .pipe(data(getJsonData))
        .pipe(ejs({},{},{ext: '.html'}))
        .pipe(gulp.dest(buildPath.html))
        .pipe(connect.reload());
});

// 监听任务
gulp.task('watch', function() {
    console.log('watch...')

    gulp.watch(srcPath.less, ['less']);   // 监听 less 文件
    gulp.watch(srcPath.ejs, ['ejs']);     // 监听 ejs 文件
    gulp.watch(srcPath.json, ['ejs']);    // 监听 json 文件
})

3.2 执行监听

$ gulp watch

// 或者(默认执行 default)
$ gulp

4、实现页面热更新

4.1 安装 gulp-connect

$ cnpm install gulp-connect --save-dev

4.2 注册热加载任务

// 热加载
gulp.task('webserver', function() {
    connect.server({
        root: ['dist'],  // 监控的目录
        port: 5000,      // 端口
        livereload: true    // 启用热加载
    });
});

4.3 文件改动时通知 livereload 刷新页面

// less 处理
gulp.task('less', function() {
    return gulp.src('src/less/*.less')
        .pipe(less())
        .pipe(gulp.dest('dist/css'))
        .pipe(connect.reload()); // css 文件更新后通知刷新页面
});

// ejs 处理
gulp.task('ejs', function() {
    return gulp.src('src/*.ejs')
        .pipe(data(getJsonData))
        .pipe(ejs({},{},{ext: '.html'}))
        .pipe(gulp.dest('./dist'))
        .pipe(connect.reload()); // html 文件更新后通知刷新页面
});

4.4 结合文件监听实现 文件改动实时刷新

/ 默认执行的任务
gulp.task('default', ['webserver', 'less', 'ejs', 'watch'], function() {
    console.log('default task ok!');
})
$ gulp  // 默认执行 default 任务

现在在浏览器中打开地址 locahost:5000,即可打开 dist 目录下的 index.html,当改动 less 或 ejs 文件并保存时,浏览器会自动刷新页面。

5、错误处理

5.1 安装 gulp-plumber

$ cnpm install gulp-plumber --save-dev

gulp-plumber 可以输出 gulp 插件发生的错误,并且进程不会退出。

5.2 使用

var gulp = require('gulp');
var less = require('gulp-less');
var plumber = require('gulp-plumber');

gulp.task('less', function() {
  return gulp.src('less/*.less')
      .pipe(plumber())      // 在插件前使用
      .pipe(less())
      .pipe(gulp.dest('./dist/css'))
});

整合源码如下

var gulp = require('gulp');
var less = require('gulp-less');
var ejs = require('gulp-ejs');
var data = require('gulp-data');
var plumber = require('gulp-plumber');
var connect = require('gulp-connect');

var path = require('path');
var fs = require('fs');

var srcPath = {
    less: 'src/less/*.less',
    ejs: 'src/*.ejs',
    json: 'src/temple/*.json'
};

var buildPath = {
    css: 'dist/css',
    html: 'dist'
};


// 默认执行的任务
gulp.task('default', ['webserver', 'less', 'ejs', 'watch'], function() {
    console.log('default task ok!');
})

// 热加载
gulp.task('webserver', function() {
    connect.server({
        root: ['dist'],  // 监控的目录
        port: 5000,
        livereload: true
    });
});

// less 处理
gulp.task('less', function() {
    return gulp.src(srcPath.less)
        .pipe(plumber())
        .pipe(less())
        .pipe(gulp.dest(buildPath.css))
        .pipe(connect.reload());
});

// getJsonData
var getJsonData = function(file) {
    // G:\workspace\glup-test\src\index.ejs
    // => src/temple/index.json
    var jsonFile = 'src/temple/' + path.basename(file.path, '.ejs') + '.json';
    var data = JSON.parse(fs.readFileSync(jsonFile));
    return data;
}
// ejs 处理
gulp.task('ejs', function() {
    return gulp.src(srcPath.ejs)
        .pipe(plumber())
        .pipe(data(getJsonData))
        .pipe(ejs({},{},{ext: '.html'}))
        .pipe(gulp.dest(buildPath.html))
        .pipe(connect.reload());
});

// 监听任务
gulp.task('watch', function() {
    console.log('watch...')

    gulp.watch(srcPath.less, ['less']);   // 监听 less 文件
    gulp.watch(srcPath.ejs, ['ejs']);     // 监听 ejs 文件
    gulp.watch(srcPath.json, ['ejs']);    // 监听 json 文件
})

Webpack 3.x 学习-第四天

Webpack 学习第七步

1、Less, Sass, Stylus 解析

1.1 解析 Less 文件

参考资料:

less-loader GitHub 地址
less 官网

安装 lessless-loader


// 简写 cnpm i -D less
cnpm install --save-dev less

// 其中 less-loader 依赖 less 服务
cnpm i -D less-loader

配置 loader

大概过程是:先用 less-loaderLess 文件进行处理生成 CSS,再用 css-loader 对生成的 CSS 进行处理(如压缩),最后用 extract-text-webpack-plugin 提取 CSS 到单独的文件。

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

module.exports = {

    // ... 省略其他配置项
    
    module: {
        rules:[
            {
                test: /\.less$/,
                use: ExtractTextPlugin.extract({
                    use: [
                        {
                            loader: 'css-loader',
                            options: {
                                minimize: true
                            }
                        },{
                            loader: 'less-loader'
                        }
                    ],
                    // publicPath 的设置需根据提取的 css 存放的目录层级进行改变
                    publicPath: '../'
                })
            }
        ]
    },
    plugins: [
        new ExtractTextPlugin({
            // 从 js 文件中提取出来的 css 文件的名称,可设置路径
            filename: 'css/[name]-[contenthash:8].css'
        })
    ]
    
}
1.2 解析 Sass 文件

参考资料:

sass-loader GitHub 地址
sass 官方文档

安装 node-sasssass-loader

cnpm i -D node-sass

// sass-loader 依赖 node-sass 
cnpm i -D sass-loader

配置 loader,并使用 extract-text-webpack-plugin 将最后生成的 CSS 文件提取到指定目录下

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

module.exports = {

    // ... 省略其他配置项
    
    module: {
        rules:[
            {
                test: /\.scss$/,
                use: ExtractTextPlugin.extract({
                    use: [
                        {
                            loader: 'css-loader',
                            options: {
                                minimize: true
                            }
                        },{
                            loader: 'sass-loader'
                        }
                    ],
                    // publicPath 的设置需根据提取的 css 存放的目录层级进行改变
                    publicPath: '../'
                })
            }
        ]
    },
    plugins: [
        new ExtractTextPlugin({
            // 从 js 文件中提取出来的 css 文件的名称,可设置路径
            filename: 'css/[name]-[contenthash:8].css'
        })
    ]
    
}
1.3 解析 Stylus 文件

参考资料:

stylus-loader GitHub 地址
stylus 官方文档

安装 stylusstylus-loader

cnpm i -D stylus

// stylus-loader 依赖 stylus
cnpm i -D stylus-loader

配置 loader,并使用 extract-text-webpack-plugin 将最后生成的 CSS 文件提取到指定目录下

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

module.exports = {

    // ... 省略其他配置项
    
    module: {
        rules:[
            {
                test: /\.styl$/,
                use: ExtractTextPlugin.extract({
                    use: [
                        {
                            loader: 'css-loader',
                            options: {
                                minimize: true
                            }
                        },{
                            loader: 'stylus-loader'
                        }
                    ],
                    // publicPath 的设置需根据提取的 css 存放的目录层级进行改变
                    publicPath: '../'
                })
            }
        ]
    },
    plugins: [
        new ExtractTextPlugin({
            // 从 js 文件中提取出来的 css 文件的名称,可设置路径
            filename: 'css/[name]-[contenthash:8].css'
        })
    ]
    
}

2、添加 CSS 前缀

参考资料:

postcss-loader GitHub 地址
postcss GitHub 地址
autoprefixer GitHub 地址

PostCSS 通过 JS 插件来扩展它的功能,配合插件使用可以检查 CSS、支持 CSS VariablesMixins 等很多功能。如搭配 autoprefixer 插件来给 CSS 自动添加浏览器前缀

2.1 安装 postcss-loaderautoprefixer
// postcss-loader 已经集成了 postcss ,不需要额外再安装 postcss
cnpm i -D postcss-loader autoprefixer

PostCSS 推荐在项目根目录下,建立一个 postcss.config.js 文件,配置如下

module.exports = {
    plugins: [
        require('autoprefixer')
    ]
}
2.2 设置项目要兼容的浏览器

autoprefixer 内部使用了 Browserslist 来定义你要兼容市场上哪些浏览器;
Browserslist 定义浏览器的写法参考:Browserslist GitHub 的介绍,浏览器数据来源 Can I Use
官方推荐把配置文件写入项目根目录下的 .browserslistrc 文件或在 package.jsonbrowserslist 项中配置,这里我们在 package.json 中进行配置

{

  // ... 忽略其他配置项
  
  "browserslist": [
    "> 1%",             // 兼容市场上浏览器份额大于 1% 的浏览器
    "last 2 versions",  // 兼容浏览器最新的前两个版本
    "not ie <= 8"       // 不兼容 IE8 及以下版本
  ]
  
}
2.3 在 Webpack 中配置 postcss-loader

以处理 CSS 文件为例:先用 postcss-loader 进行处理,再用 css-loader 进行处理

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

module.exports = {

    // ... 省略其他配置项
    
    module: {
        rules: [
            {
                // 用正则去匹配要用该 loader 转换的 CSS 文件
                test: /\.css$/,
                use: ExtractTextPlugin.extract({
                    // 转换 css 文件需要使用的 Loader
                    use: [
                        {
                            loader: 'css-loader',
                            options: {
                                // 在 css-loader 之前有多少个 loader 应该对 @import 资源进行解析
                                importLoaders: 1,
                                minimize: true
                            }
                        },
                        {
                            loader: 'postcss-loader'
                        }
                    ],
                    // publicPath 的设置需根据提取的 css 存放的目录层级进行改变
                    publicPath: '../'
                })
            }
        ]
    },
    plugins: [
        new ExtractTextPlugin({
          // 从 js 文件中提取出来的 css 文件的名称,前面可加存放路径
          filename: `css/[name]-[contenthash:8].css`,
        })
    ]
}

处理 Less, Sass, Stylus 文件也是一样:
先用对应 Loader 处理后,再用 postcss-loader 处理,最后用 css-loader 处理

以处理 Less 文件为例

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

module.exports = {

    // ... 省略其他配置项
    
    module: {
        rules:[
            {
                test: /\.less$/,
                use: ExtractTextPlugin.extract({
                    use: [
                        {
                            loader: 'css-loader',
                            options: {
                                // 在 css-loader 之前有多少个 loader 应该对 @import 资源进行解析
                                importLoaders: 2,
                                minimize: true
                            }
                        },
                        {
                            loader: 'postcss-loader'
                        },
                        {
                            loader: 'less-loader'
                        }
                    ],
                    // publicPath 的设置需根据提取的 css 存放的目录层级进行改变
                    publicPath: '../'
                })
            }
        ]
    },
    plugins: [
        new ExtractTextPlugin({
            // 从 js 文件中提取出来的 css 文件的名称,可设置路径
            filename: 'css/[name]-[contenthash:8].css'
        })
    ]
    
}

3、消除未使用的 CSS

当项目中使用到 CSS 框架(如:Bootstrap)时,很多 CSS 代码是用不到的,这就需要消除未使用的 CSS
需要使用 Webpackpurifycss-webpack 插件,其依赖 purify-css 来移除未使用的 CSS,它必须和 extract-text-webpack-plugin 插件一起使用

参考资料:

purifycss-webpack GitHub 地址
purify-css GitHub 地址

3.1 安装 purifycss-webpackpurify-css
cnpm i -D purifycss-webpack purify-css
3.2 配置 purifycss-webpack

purifycss-webpack 必须要配合 extract-text-webpack-plugin 来使用

const ExtractTextPlugin = require('extract-text-webpack-plugin');
const PurifyCssPlugin = require('purifycss-webpack');
const glob = require('glob');   // 用来同步检查 html 模板

module.exports = {

    // ... 省略其他配置项
    module: {
        rules: [
            {
                // 用正则去匹配要用该 loader 转换的 CSS 文件
                test: /\.css$/,
                use: ExtractTextPlugin.extract({
                    // 转换 .css 文件需要使用的 Loader
                    use: [
                        {
                            loader: 'css-loader',
                            options: {
                                minimize: true
                            }
                        }
                    ],
                    // publicPath 的设置需根据提取的 css 存放的目录层级进行改变
                    publicPath: '../'
                })
            }
        ]
    },
    plugins: [
        new ExtractTextPlugin({
            // 从 js 文件中提取出来的 css 文件的名称,可设置路径
            filename: 'css/[name]-[contenthash:8].css'
        }),
        // 确保 purifycss-plugin 在 extract-text-webpack-plugin 后面
        new PurifyCssPlugin({
            // 必须写绝对路径,同步检查 html 模板
            paths: glob.sync(path.resolve(__dirname, 'src/*.html'))
        })
    ]
    
}

Webpack 学习第八步

1、支持 ES6

1.1 babel-loader 转换 ES6

ECMAScript 6.0 是 2015 年发布的下一代 JavaScript 语言标准,它引入了新的语法和 API 来提升开发效率。

目前部分浏览器已经支持 ES6 ,但并不全面。所以在开发过程中,需要把采用 ES6 语法编写的 JS 代码转换为 ES5 语法,在 Webpack 中使用 babel-loader 来对 JS 进行转换。

参考资料:

babel-loader GitHub 地址
Babel GitHub 地址
Babel 官方文档

安装 babel-loaderbabel-core

// babel-loader 依赖 babel-core
// babel-core 是 Babel 核心
cnpm i -D babel-core babel-loader 
1.2 Babel 配置

babel-loader 通过调用 Babel 完成转换工作,我们还需对 Babel 进行配置,告诉它如何转换 JS 语法,Babel 的配置文件在项目根目录下的 .babelrc 中,格式如下:

{
    
    "plugins": [
        [
            "transform-runtime",
            {
                "polyfill": false
            }
        ]
    ],
    "presets": [
        [
            "env", 
            {
                "targets": {
                    "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
                }
            }
        ],
        "stage-2"
    ]
}

其中:

1、plugins 属性告诉 Babel 要使用哪些插件,插件可以控制如何转换代码

transform-runtime 对应的插件为 babel-plugin-transform-runtime,其作用是减少冗余代码,配合 babel-runtime 一起使用。

参考:transform-runtime 文档

2、presets 属性告诉 Babel 要对哪些语法特性进行转换,并用对应插件进行转换

env 对应的插件为 babel-preset-env,根据你支持的环境自动决定使用适合的 Babel 插件(可转换当前所有 ECMAScript 标准里的最新特性)

参考:env 文档

stage-2 对应的插件为 babel-preset-stage-2,所有使用 stage 2 (或更高)阶段的代码必备的插件 (stage 2 阶段: 是指已经被起草的特性规范,将会被纳入到标准里)

参考:stage-2 文档

Babel 配置文件中的执行顺序为:
先执行 `plugins` 再执行 `presets`
其中 `plugins` 配置顺序执行,`presets` 配置逆序执行
1.3 安装相应插件
cnpm i -D babel-plugin-transform-runtime
cnpm i -S babel-runtime

cnpm i -D babel-preset-env
cnpm i -D babel-preset-stage-2
1.4 配置 babel-laoder
module.exports = {

    // ... 省略其他配置项
    
    // 模块:例如转换 es6, less, 图片转换等...
    module: {
        rules:[
        
            // ...省略其他配置项
            
            {
                test: /\.js$/,
                use: [{
                    loader: 'babel-loader'
                }]
            }
        ]
}

Git 工作流规范

Git 工作流规范

Git 学习资料

在阅读该开发规范前需熟悉 Git 的使用,推荐如下链接:

1、Pro Git 简体中文版 (很好的学习 Git 的书)
2、git-recipes (GitHub 上开源的手册,图文并茂)

三篇关于 Git 工作流的参考文章:

1、Git 工作流程
2、常见工作流比较 (这个是上面 git-recipes 中的文章)
3、简介我的 Git Work Flow

规范不是金科玉律,实际情况可能更加复杂,大家可以根据实际情况调整规范

1、分支规范

1.1 master 分支(主分支):存储官方发布历史
1.2 develop 分支(开发分支):用来整合各功能分支

image

1.3 feature 分支(特性/功能分支):每个新功能都放置在自己的分支中,可以在备份/协作时推送到**仓库

feature 分支develop 分支 作为父分支,当一个功能完成时,它将被合并回 develop 分支。(功能永远不应该直接在 master 分支 上交互 )

image

1.4 release 分支(发布分支):一旦 develop 分支 的新功能足够发布(或者预先确定的发布日期即将到来),从 develop 分支 fork 的 一个分支。

release 分支 的创建开始了下个发布周期,只有和发布相关的任务应该在这个分支进行,如修复 bug、生成文档等。

一旦准备好发布,发布分支将合并进 master 分支,并打上版本号的标签。另外,它也应该合并回 develop 分支

发布分支命名规范:release-* or release/*

image

1.5 bugfix 分支:修复在 release 分支 上发现的 bug(类似 hotfix 分支

标准的 GitFlow 是没有该分支的,因为可以直接在 release 分支 上提交 commit 来修复 bug
为了修复 bug 的方便,我加了这个分支规范,可以根据自己的开发规范来决定是否需要

image

1.6 hotfix 分支(维护/补丁分支):用来快速给产品的发布打上补丁

这是唯一可以从 master 分支 上 fork 的分支,一旦修复完成了,它应该被并入 masterdevelop 分支,master 分支 应该打上更新的版本号的标签。

有一个专门的 bug 修复开发线使得你的团队能够处理 issues,而不打断其他工作流或是要等到下一个发布周期。你可以将维护分支看作在 master 分支 上工作的临时发布分支。

image

2、流程规范

创建本地仓库

创建本地仓库后,一般还需要进行一些设置,来与远程仓库进行交互

有两种方式:本地新建仓库 或者 克隆远程仓库

方法一 : 本地新建仓库

1、新建并初始化本地仓库

// 创建项目目录,如 Development-spec
$ mkdir Development-spec

// 进入项目目录 
$ cd Development-spec

// 初始化目录,把目录变成 Git 可以管理的仓库
// 这时当前目录下多了一个.git的目录,这个目录是Git来跟踪管理版本库的
$ git init

git init 命令默认创建了本地 master 分支,所以几乎每一个仓库都有 master 分支

注意:
master 分支 类似于新建的孤儿分支,没有任何提交信息
使用 git branch -v 查看分支时,显示不出来
一旦有提交历史,就成为真正的分支,可以查看到

创建孤儿分支命令:git checkout --orphan <branch>
孤儿分支意思为该分支中没有任何内容,与之前创建的其他分支没有任何关联

之后所有 git 命令 都是在本地仓库目录 Development-spec 下执行

2、关联远程仓库

git remote add <name> <url>

name: 作为远程仓库的别名(一般命名为 origin)
url: 远程仓库地址,支持 HTTPS 和 SSH 协议

HTTPS 是允许匿名、只读访问仓库的简易方式

如果希望对仓库进行读写,你需要使用 SSH 协议,而且需要在托管的服务器上有一个有效的 SSH 账户
$ git remote add origin [email protected]:shushu2013/Development-specification.git

一些操作远程仓库的命令

$ git remote                // 列出本地仓库和其他远程仓库的连接
$ git remote -v             // 与 git remote 相同,但同时显示每个连接的 URL
$ git remote show <name>    // 查看远程仓库信息,name: 为远程仓库的别名

$ git remote rm <name>      // 移除名为 name 的远程仓库的连接
$ git remote rename <old-name> <new-name>   // 将远程仓库别名从 old-name 重命名为 new-name

3、从远程仓库抓取数据

$ git fetch

此命令会从所有远程仓库中拉取本地仓库中没有的数据
你就可以在本地访问所有远程仓库中的所有分支,分支以远程仓库名开头
例如:远程仓库 origin 中的 master 分支,fetch 后在本地对应的分支名字是 origin/master
你可以将其中某个分支合并到本地,或者切换到这个分支,看看有些什么有趣的更新

git fetch 详细命令

$ git fetch <remote>

此命令会到远程仓库 remote 中拉取所有你本地仓库中还没有的数据
运行完成后,你就可以在本地访问该远程仓库中的所有分支,将其中某个分支合并到本地,或者只是取出某个分支,一探究竟

$ git fetch <remote> <branch>

和上一个命令相同,但是指定了远程仓库的分支名

4、跟踪远程分支

跟踪远程分支:将本地分支和某个远程分支进行关联

关联后,该本地分支也称为 跟踪分支(tracking branch)
在该分支中输入 git push,Git会自行推断应该向哪个服务器的哪个分支推送数据
同样,在该分支里运行 git pull 会获取远程分支索引,并把它的数据都合并到本地分支中来

设定跟踪分支相关命令

1、当分支不存在,或者是新建的孤儿分支,且没有任何提交历史,可使用如下命令,否则会报错

$ git checkout -b <branch> <remote-branch>

branch: 本地分支
remote-branch: 远程分支

该命令会创建 branch 分支,设置 branch 分支跟踪远程分支 remote-branch,并切换到 branch 分支

$ git checkout --track/-t <remote-branch>

--track/-t : -t 是 --track 命令的简写
remote-branch : 本地 fetch 下来的远程分支

该命令是 git checkout -b <branch> <remote-branch> 的简化版本,默认创建的本地分支为 remote-branch 的 branch
如:创建本地的 master 分支设定为跟踪远程仓库 origin 的 master 分支,并切换到 master 分支
$ git checkout -t origin/master

2、当分支存在,并且已有提交历史,可使用如下命令

$ git branch --set-upstream-to=<remote-branch>  <branch>

该命令设置本地 branch 分支跟踪远程分支 remote-branch

注意:设置后,因为分支已经有提交历史,可能需要运行 git pull 或 git merger 命令来合并远程仓库的数据
在 2.9.0 之后,会报错:fatal: refusing to merge unrelated histories (拒绝合并历史无关的分支)
需要添加 --allow-unrelated-histories 选项
$ git merge <remote-branch> <branch> --allow-unrelated-histories
这样会产生一个新的合并提交历史
方法二:克隆远程仓库
$ git clone <url>               // 下载一个项目和它的整个代码历史
$ git clone <url> <directory>   // 指定下载目录

// 会在当前目录下创建一个 Development-specification 目录,包含了对远程仓库的克隆
$ git clone https://github.com/shushu2013/Development-specification.git

克隆远程仓库的好处:
1、本地仓库默认关联了远程仓库,远程仓库别名为 origin
2、远程仓库的所有分支都下载到了本地,可通过 git branch -r 查看
2、默认创建了 master 分支 并设置为 origin/master 远程分支的跟踪分支

创建 develop 分支
// 以 master 分支创建一个互补的 develop 分支
$ git branch develop master

// 把 develop 分支推送到服务器上
$ git push -u <remote> develop

develop 分支 将会包含项目中所有的历史,而 master 分支 将包含不完全的版本
其他开发者应该将**仓库克隆到本地,创建一个分支来追踪 develop 分支

$ git clone [email protected]:shushu2013/Development-specification.git
$ git checkout -b develop origin/develop
创建 feature 分支

feature 分支 都应该基于 develop 分支,而不是 master 分支

$ git checkout -b <some-feature> develop

开发者使用 [ 编辑、缓存、提交 ] 的一般约定来向功能分支 feature 分支 添加提交:

$ git status    // 查看当前文件状态

$ git add <file>    // 跟踪新文件、暂存已修改文件
$ git add .         // . 表示所有文件

$ git commit -m "<commit-message>"  // 提交更新

在添加了一些提交之后,开发者确信开发的功能已经准备好了
如果团队使用 Pull Request,现在正是发起 Pull Request 的好时候,请求将 feature 分支 并入 develop 分支
否则,可以向下面一样,将它并入本地的 develop 分支,然后推送到**仓库:

// 一般在远程仓库的 develop 分支会有频繁提交,需在本地更新,并解决合并冲突
$ git pull <remote> develop      // 更新本地的 develop 分支数据
$ git checkout <some-feature>   // 切换到 some-feature 分支
$ git rebase develop            // 把整个 some-feature 分支移动到 develop 分支的后面

在合并之前,在 some-feature 分支使用 rebase 命令:
它会把整个 some-feature 分支移动到 develop 分支的后面,有效地把所有 develop 分支上新的提交并入过来
但是,rebase 为原分支上每一个提交创建一个新的提交,重写了项目历史,并且不会带来合并提交
参考:5.1-代码合并:Merge、Rebase-的选择

$ git checkout develop              // 切换到 develop 分支
$ git merge --no-ff <some-feature>  // 将功能分支数据合并到 develop 分支,--no-ff : 表示不使用默认的快速合并
$ git push                          // 推送 develop 分支数据到远程仓库的 develop 分支

---

$ git branch -d <some-feature> // 删除功能分支    
创建 release 分支

当功能开发完成或者开发周期结束,准备项目的官方发布时,新建一个 release 分支 来封装发布的准备工作
这也正是发布的版本号创建的一步:

// 从 develop 分支 fork 一个 release 分支
$ git checkout -b <release-version> develop

// 把 release 分支推送到服务器上,当正式发布到 master 分支后,可以删除本地和服务器上的 release 分支
$ git push -u <remote> <release-version>

这个 release 分支 用来整理提交,充分测试,更新文档,为即将到来的发布做各种准备
它就像是一个专门用来完善发布的功能分支,期间添加的 commit 基本都是 bug fix
开发结束后同时合并进 develop 分支master 分支,并在 master 分支 打上发布 tag
最后可以删除本地和服务器上的 release 分支

创建 bugfix 分支

release 分支 的 bug 修复
可以直接提交 commit 修复 bug,或者在本地开一个 bugfix 分支(类似 hotfix 分支),修复好后合并进 release 分支,然后删除 bugfix 分支

$ git pull <remote> <release-version>               // 从远程仓库更新 release-version 分支数据                  
$ git checkout -b <bugfix-issue> <release-version>  // fork 一个 bugfix-issue 分支,进行 bug 修复

--- bug 修复后

// 以下三步在远程仓库的 release-version 分支有频繁提交的情况下才需要
$ git pull <remote> <release-version>    // 更新 release-version 分支
$ git checkout <bugfix-issue>            // 切换到 bugfix-issue 分支
$ git rebase <release-version>           // 把整个 bugfix-issue 分支移动到 release-version 分支的后面

$ git checkout <release-version>    // 切换到 release-version 分支
$ git merge --no-ff <bugfix-issue>  // 把 bugfix-issue 分支的数据合并进 release-version 分支
$ git push                          // 推送 release-version 分支到远程仓库

$ git branch -d <bugfix-issue>      // 删除 bugfix-issue 分支

最后 release 分支 合并进 master 分支develop 分支

$ git pull <remote> <release-version>   // 更新 release-version 分支
$ git checkout master                   // 切换到 master 分支
$ git merge --no-ff <release-version>   // 将 release-version 分支合并进 master 分支
$ git tag <tag-version>                 // 打上对应的版本标签

$ git push                          // 推送到远程仓库,默认不会把标签推送上去
$ git push <remote> <tag-version>   // 把 tag-version 标签推送到远程仓库

-----
$ git pull <remote> develop         // 更新 develop 分支
$ git checkout <release-version>    // 切换到 release-version 分支
$ git rebase develop                // 把整个 release-version 分支移动到 develop 分支的后面

$ git checkout develop                  // 切换到 develop 分支
$ git merge --no-ff <release-version>   // 合并进 develop 分支,保证将来的版本也会包含 release-version 上的 bug 修复
$ git push                              // 推送到远程仓库

---- 正式发布完 master 分支

$ git branch -d <release-version>       // 删除本地 release-version 分支
$ git push <remote> :<release-version>  // 删除远程 release-version 分支

标签相关额外命令介绍,更多参考 [ Git 基础 - 打标签 ]

// 列出所有标签
$ git tag

// 打上含附注的标签
$ git tag -a <tag-version> -m <tag-message>

-m :选项则指定了对应的标签说明,Git会将此说明一同保存在标签对象中
    如果没有给出该选项,Git会启动文本编辑软件供你输入标签说明

// 一次推送所有本地新增的标签上去
$ git push origin --tags

创建 hotfix 分支

hotfix 分支 创建时间在项目正式发布之后

发布后,用户发现了 bug,这时从 master 分支 fork 一个 hotfix 分支,修复完成后,合并回 master 分支develop 分支,最后删除这个 hotfix 分支

// 从 master 分支 fork 一个 hotfix-issue 分支,并切换到 hotfix-issue 分支
$ git checkout -b <hotfix-issue> <master>

--- 修复 bug

// 开发者使用 [ 编辑、缓存、提交 ] 的一般约定来向修复 hotfix-issue 分支 添加提交:
$ git checkout <hotfix-issue>   // 切换到 hotfix-issue 分支
$ git status                    // 查看当前文件状态

$ git add <file>    // 跟踪新文件、暂存已修改文件
                    // 或者使用 git add . 命令,其中 . 表示所有文件

$ git commit -m "<commit-message>"  // 提交更新

--- 修复完成,合并回 master 分支 和 develop 分支

$ git checkout master
$ git merge --no-ff <hotfix-issue>
$ git tag <tag-version>             // 打上标签
$ git push
$ git push <remote> <tag-version>

$ git checkout develop
$ git pull
$ git merge --no-ff <hotfix-issue>
$ git push

--- 删除 hotfix-issue 分支

$ git branch -d <hotfix-issue>

前端跨域方法

参考

1、前端跨域常用方法
2、前端跨域整理

域名

域名:用于识别网站
例如:baidu.com、github.com 这个是一个一级域名

二级域名:是在一级域名之下的域名,比如 domain.baidu.com 就是一个二级域名,
我们常见的 www.baidu.com也是一个二级域名,不过由于大家通常都把 www 当作是通用域名,
因此 www.baidu.com 一般被看作是一级域名。

子域名:xxx + 域名,例如:map.baidu.com、support.github.com
子域名无需申请,当你购买了你自己的域名后,你就可以在你的域名前加上任何开头形成子域名。
例如:map.baidu.com 是 baidu.com 的子域名

跨域

只要协议、域名、端口有任何一个不同,都被当作是不同的域

https://www.baidu.com       vs  http://www.baidu.com        协议不同
https://www.baidu.com       vs  https://map.baidu.com       域名不同
https://www.baidu.com:8080  vs  https://www.baidu.com:5000  端口不同

注意

1、如果是协议和端口造成的跨域问题“前台”是无能为力的

2、在跨域问题上,域仅仅是通过“URL的首部”来识别
("URL的首部":window.location.protocol + window.location.host)

1、通过 jsonp 跨域

1.1 原理:

通过动态加载 JS 来实现,这种方式允许用户传递一个 callback 参数给服务端,然后服务端返回数据时会将这个 callback 参数作为函数名来包裹住 JSON 数据。

1.2 实现:

客户端

// 回调函数
function doSomething(data) {
    console.log('The author is: ' + data.name);
}
// 动态创建 JS
var script = document.createElement('script');
//与服务器端约定 callback 参数,用来指定回调函数的名字
script.src = 'http://www.wenbo.com/author?callback=doSomething';
document.body.appendChild(script);

服务器端

// 服务器收到 http://www.wenbo.com/author?callback=doSomething 请求后
// 会将数据放到回调函数的参数位置返回
// doSomething({"name": "wenbo"});

// 假如服务器是 NodeJS,核心代码如下
const data = {name: 'wenbo'}
// 获取查询参数callback,就是我们在客户端定义的 doSomething
const callback = url.parse(req.url, true).query.callback
if (callback) {
    const str = callback + '(' + JSON.stringify(data) + ')'
    // 以字符串的形式返回
    res.end(str)
} else {
    res.end('hello world')
}

1.3 利用 jQuery 的 $.getJSON 进行跨域请求

该方法会自动判断是否跨域,不跨域就调用普通的 ajax 方法;跨域的话,则会以异步加载 js 文件的形式来调用 jsonp 的回调函数。

// jquery 会自动生成一个全局函数来替换 callback=? 中的 ?, 之后获取到数据又会自动销毁,实际上就是起一个临时代理函数的作用。
$.getJSON('http://www.wenbo.com/author?callback=?', function(data) {
    console.log('The author is: ' + data.name);
})

1.4 优缺点

兼容性更好,在老浏览器中都可以运行。
只支持 GET 请求而不支持 POST 等其它类型的 HTTP 请求;不能解决不同域的两个页面之间如何进行 JavaScript 调用问题。

2、通过 CORS 跨域

参考:跨域资源共享 CORS 详解

CORS ( Cross-Origin Resource Sharing ) 跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何通信。

实现 CORS 通信的关键是服务器,只要服务器实现了 CORS 接口,就可以跨源通信。

2.1 基本**:

使用自定义的 HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。目前,所有浏览器都支持该功能,IE 浏览器不能低于 IE10。整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。

2.2 通信

服务器端:

服务器端对于 CORS 的支持,主要就是通过设置 Access-Control-Allow-Origin 来进行的。

客户端:

正常的 Ajax 请求,浏览器一旦发现 Ajax 请求跨源,就会自动添加一些附加的信息,有时还会多出一次附加的请求,但用户不会有感觉。

var xhr = new XMLHttpRequest();
xhr.open("GET", "http://www.wenbo.com/author",true);
xhr.onreadystatechange = function() {
    if (xhr.readyState == 4) {
        if (xhr.status == 200) {
            console.log(xhr.responseText);
        } else {
            console.log('Request status: ' + xhr.status);
        }
    }
}
xhr.send();

2.3 优点

CORS 支持所有类型的 HTTP 请求,开发者可以使用普通的 XMLHttpRequest 发起请求和获取数据,比 JSONP 有更好的错误处理。

3、WebSocket 通信

WebSocket 是一种 HTML5 的一种新的协议 (IE10+),它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案,详细参考 MDN

/* websocket协议为ws/wss, 类似http/https的区别 */
wsUrl = 'wss://127.0.0.1:8090/ws/';
/* 发送 */
ws = new WebSocket(wsUrl);
/* 连接成功建立时调用 */
ws.onopen = function (event) {
  console.log("websocket command onopen");
  var msg = {
    username: 'YeaseonZhang'
  }
  /* 通过 send() 方法向服务端发送消息,参数必须为字符串 */
  ws.send(JSON.stringify(msg));
};
/* 服务端向客户端发送消息时调用 */
ws.onmessage = function (event) {
  /* event.data包含了服务端发送过来的消息 */
  console.log("websocket command onmessage: " + event.data);
  if (event.data === 'success') {
    /* 通过 close() 方法断开websocket连接 */
    ws.close();
  }
};
/* 连接被关闭时调用 */
ws.onclose = function (event) {
  console.log("websocket command onclose: " + event.data);
};
/* 出现错误时调用 */
ws.onerror = function (event) {
  console.log("websocket command onerror: " + event.data);
};

4、document.domain + iframe 跨域

4.1 例子

有一个页面地址是 http://www.a.com/a.html, 在这个页面里有一个 iframe, 它的
src 是 http://support.a.com/a.html, 很显然它们的域是不同的,所以我们无法在http://www.a.com/a.html页面中通过 js 来获取 iframe 中的东西.

这个时候我们只需要把 http://www.a.com/a.htmlhttp://support.a.com/a.html 这两个页面的 document.domain 都设成相同的域名就可以了。注意:document.domain 只能设置为自身或更高一级的父域,且主域必须相同。

在页面 http://www.a.com/a.html 中设置 document.domain

<iframe id = "iframe" src="http://support.a.com/a.html" onload = "test()"></iframe>
<script type="text/javascript">
    var username = 'wenbo';
    document.domain = 'a.com'; //设置成主域
    function test(){
        alert(document.getElementById('iframe').contentWindow);
        //contentWindow 可取得子窗口的 window 对象
    }
</script>

在页面 http://support.a.com/a.html 中设置 document.domain

<script type="text/javascript">
    //在iframe载入这个页面也设置document.domain,使之与主页面的document.domain相同
    document.domain = 'a.com';
    console.log(window.parent.username) // 访问父窗口成员
</script>

5、location.hash

5.1 原理

父窗口可以对 iframe 进行 URL 读写,iframe 也可以读写父窗口的 URL,URL 有一部分被称为 hash(#号及其后面的字符),此方法的原理就是改变 URL 的 hash 部分来进行双向通信。(由于 IE、Chrome 在不同域下不允许修改 parent.location.hash 的值,所以要借助于父窗口域名下的一个代理 iframe 来通信)
wode

5.2 例子

a页面:http://www.a.com/a.html

<iframe id="iframe-b" src="http://www.b.com/b.html"></iframe>
<script>
  var bPage = document.getElementById('iframe-b');
  /* 修改iframe 下的 hash */
  bPage.src = bPage.src + '#user=yeaseonzhang';
  function cb (res) {
    console.log(res);
  }
</script>

b页面:http://www.b.com/b.html

<iframe id="iframe-c" src="http://www.a.com/c.html"></iframe>
<script>
  var cPage = document.getElementById('iframe-c'); 
  /* 修改iframe 下的 hash */
  cPage.src = cPage.src + '#age=20';
  window.onhashchange = function () {
    /* 监听 hash 变化,改变 c 页面的 hash */
    cPage.src = cPage.src + location.hash;
    // doSomething...
  }
</script>

c页面:http://www.a.com/c.html

<script>
  window.onhashchange = function () {
    /* 监听 hash 变化,调用 a 页面的函数 */
     window.parent.parent.cb('success: ' + location.hash.substring(1));
  }
</script>

由于 a页面和 c页面是同域资源,所以 c页面可以通过 window.parent.parent 访问 a页面资源。

5.3 优缺点

改变 hash 会产生浏览器历史记录,有些浏览器不支持(IE8+ 支持) onhaschange 事件,需要轮询来获知 URL 的改变。数据直接暴露了 URL 中,且数据容量和类型有限。

6、window.name

6.1 原理

window 对象有个 name 属性,该属性有个特征:在一个窗口的生命周期内,窗口载入的所有的页面都是工享一个 window.name 的,每个页面对 window.name 都有读写的权限,且 window.name 并不会因新页面的载入而进行重置。

类似 location.hash,需要借助第三个同域页面辅助

6.2 例子

页面a: http://www.a.com/a.html

<script>
  var iframe = document.createElement('iframe');
  /* step 1 加载跨域页面 */
  iframe.src = 'http://www.b.com/b.html';
  var domain = 'diff';
  /* 监听iframe加载 */
  iframe.onload = function () {
    if ('diff' == domain) {
      /* step 2 重定向到同域页面 */
      iframe.contentWindow.location = 'http://www.a.com/c.html';
      domain = 'same';
    } else if ('same' == domain) {
      /* 获取同域资源的window.name信息 */
      cb(iframe.contentWindow.name);
      /* 清空数据 */
      iframe.contentWindow.name = '';
    }
  }
  function cb (res) {
    console.log(JSON.parse(res));
  }
</script>

页面b: http://www.b.com/b.html

<scirpt>
  /* 写入相关数据 */
  var obj = {
    username: 'wenbo'
  }
  window.name = JSON.stringify(obj);
</script>

页面c: http://www.a.com/c.html

同域 c页面,可以是一个空页面,不需要进行任何操作。

7、postMessage

7.1 原理

利用 HTML5 的 postMessage 方法跨域(IE8+支持),该方法包括接受信息的"message"事件和发送信息的"postMessage"方法。

参考:MDN

window.postMessage(message, targetOrigin, [transfer])

window: 指目标窗口 (window.frames属性的成员或由window.open方法创建的窗口),也就是给哪个window发送消息。

message: 消息内容

targetOrigin: 接受消息窗口的源,即 "协议 + 域名 + 端口"。也可以设置为通配符 *,向所有窗口发送

transfer: 可选参数(布尔值),是一串和 message 同时传递的 `Transferable` 对象。这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

发送者和接收者都可以通过监听 message 事件来接收信息。
message 事件的事件对象 event 包含三个属性:

event.source: 发送消息的窗口对象的引用,可以用此在两个窗口建立双向通信
event.origin: 发送消息的 URL
event.data:   消息内容

7.2 例子

发送者: http://www.a.com/a.html

<script>
  var newWindow = window.open('http://www.b.com/b.html');
  /* 向b.html发送消息 */
  newWindow.postMessage('Hello', 'http://www.b.com/b.html');
  /* 双向通信,接收b.html的回复消息 */
  var onmessage = function (event) {
    var data = event.data;
    var origin = event.origin;
    var source = event.source;
    if (origin == 'http://www.b.com/b.html') {
      console.log(data); //Nice to see you!
    }
  };
  // 监听 message 消息
  window.addEventListener('message', onmessage, false);
</scirpt>

接收者:http://www.b.com/b.html

<script>
  var onmessage = function (event) {
    var data = event.data;
    var origin = event.origin;
    var source = event.source;
    if (origin == 'http://www.a.com/a.html') {
      console.log(data); //Hello
      /* 回复a.html的消息 */
      source.postMessage('Nice to see you!', 'http://www.a.com/a.html');
    }
  };
  // 监听 message 消息
  window.addEventListener('message', onmessage, false);
</script>

或者

a页面:http://www.a.com/a.html

<script type="text/javascript">
var ifr = document.createElement('iframe');
ifr.src = 'https://www.b.com/b.html';
document.body.appendChild(ifr);

window.onload = function() {
   // 若写成'http://www.b.com/c/proxy.html'效果一样
   // 若写成'http://www.c.com'就不会执行postMessage了
    var targetOrigin = 'https://www.b.com'; 
    ifr.contentWindow.postMessage('I was there!', targetOrigin);
};
</script>

b页面:http://www.b.com/b.html

<script type="text/javascript">
    window.addEventListener('message', function(event){
        // 通过origin属性判断消息来源地址
        if (event.origin == 'http://www.a.com') {
            alert(event.data);    // 弹出"I was there!"  
            
            // 对a.com、index.html中window对象的引用
            // 但由于同源策略,这里event.source不可以访问window对象
            alert(event.source);
        }
    }, false);
</script>

Webpack 3.x 学习-第三天

Webpack 学习第五步

前言提示

说明

经过前两天的学习总结,我们把配置文件分为 开发环境配置生产环境配置,因为 开发环境配置 相对简单,所有在之后的学习中我们统一修改的是 生产环境配置 文件。

避免由于版本号带来的坑

在使用各种 Webpack 的插件和 Loader 时,一定要注意使用的版本号,因为不同版本有些配置项可能不同。

那如何找到对应版本的文档呢?

方法一:

去项目的 GitHub 仓库地址,可以选择代码的 Branche 中的 Tags 标签来选择对应的代码版本,一般都有对应的使用文档。

方法二:

如果方法一不起作用,那可以去项目的官方网站,结合其中的文档和更新日志来查看对应版本有哪些改变的地方。

1、处理图片

需求:
图片可能在 CSS, JS, HTML 中使用,如下所示

// 在 css 中使用图片
.bg {
    background-image: url('../image/index/001.jpg');
}

// 在 js 中使用图片
function addImg() {
    var imgUrl = require('../image/index/001.jpg');
    var img = document.createElement('img');
    img.src = imgUrl;

    document.body.appendChild(img);
}

// 在 html 中使用图片
<img src="image/index/001.jpg" >

我们需要把这些图片提取出来,并且放到指定目录下。

1.1 使用到的插件:

file-loader:解析提取 CSS, JS 中的图片
url-loader:类似 file-loader,可设置把图片文件转为 Base64 的格式写入 CSSJS
html-withimg-loader:解析提取 HTML 中的 img 标签引用的图片

参考资料:

file-loader GitHub 地址
url-loader GitHub 地址
html-withimg-loader GitHub 地址

1.2 使用 file-loader

安装:

cnpm i -D file-loader

配置:

module.exports = {

    // ... 省略其他配置项
    
    // 模块:例如转换 es6, less, 图片转换等...
    module: {
        rules:[
        
            // ...省略其他配置项
            
            {
                test: /\.(png|jpg|gif|svg)$/,
                use: [
                    { 
                        loader: 'file-loader',
                        options: {
                            name: '[name]-[hash:8].[ext]',
                            // 提取出来的图片放置在 image 目录下,后面的 / 不能省略
                            outputPath: 'image/'
                        }
                    }
                ]
            }
        ]
}
1.3 使用 url-loader

安装:

cnpm i -D url-loader

配置:

module.exports = {

    // ... 省略其他配置项
    
    // 模块:例如转换 es6, less, 图片转换等...
    module: {
        rules:[
        
            // ...省略其他配置项
            
            {
                test: /\.(png|jpg|gif|svg)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 5120,
                            name: '[name]-[hash:8].[ext]',
                            outputPath: 'image/'
                        }
                    }
                ]
            }
        ]
    }
}

注意:url-loader 封装了 file-loader,使用时并不需要再安装 file-loader

limit 参数(字节/Byte):

当图片文件小于设定的值时,会把文件转换为 DataURL(Base64 格式)
当图片文件大于设定的值时,会默认调用 file-loader 进行处理,参数也会直接传给 file-loader,可通过 fallback 参数指定其他 loader 来处理

1.4 使用 html-withimg-loader

html-withimg-loader 处理 HTML 文件中 <img> 标签引用的图片

安装:

cnpm i -D html-withimg-loader

配置:

module.exports = {

    // ... 省略其他配置项
    
    // 模块:例如转换 es6, less, 图片转换等...
    module: {
        rules:[
        
            // ...省略其他配置项
            
            {
                test: /\.(htm|html)$/i,
                use: ['html-withimg-loader']
            }
        ]
    }
}

2、提取出来的 CSS 中图片路径不对

2.1 问题描述

当用 extract-text-webpack-plugin 插件提取 CSS 并指定存放目录时,生成的 CSS 中图片的引用路径并不正确
(注意:如果不指定 CSS 存放的目录,图片引用路径是正确的)

例如 extract-text-webpack-plugin 及图片处理配置如下:

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

module.exports = {

    // 入口文件的配置项
    entry: {
        index: './src/js/index.js'
    },
    // 出口文件的配置项
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'js/[name]-[chunkhash:8].js'
    },

    // ... 省略其他配置项
    
    module: {
        rules: [
            {
                // 用正则去匹配要用该 loader 转换的 CSS 文件
                test: /\.css$/,
                use: ExtractTextPlugin.extract({
                    // 使用 css-loader 对 css 进行压缩
                    use: [
                        {
                            loader: 'css-loader',
                            options: {
                                minimize: true
                            }
                        }
                    ]
                })
            },
            {
                test: /\.(png|jpg|gif|svg)$/,
                use: [
                    { 
                        loader: 'file-loader',
                        options: {
                            name: '[name]-[hash:8].[ext]',
                            // 提取出来的图片放置在 image 目录下,后面的 / 不能省略
                            outputPath: 'image/'
                        }
                    }
                ]
            }
        ]
    },
    plugins: [
        new ExtractTextPlugin({
          // 从 js 文件中提取出来的 css 文件的名称,前面可加存放路径
          filename: `css/[name]-[contenthash:8].css`,
        })
    ]
}

原始 main.css 内容及目录结构

.bg {
    background-image: url('../image/index/001.jpg');
}
|-- src
      |-- css
      |    |__ main.css
      |
      |-- image
            |--index
                 |__ 001.jpg

打包构建后生成的 index-5c5d3df7.css 内容及目录结构

.bg {
    background-image: url('image/001-de229472.jpg');
}
|-- dist
      |-- css
      |    |__ index-5c5d3df7.css
      |
      |-- image
            |__ 001-de229472.jpg

显然在 index-5c5d3df7.css 文件中是引用不到 001-de229472.jpg

如果我们不指定提取后 CSS 文件的存放目录,则默认生成在项目打包构建后的输出目录下

|-- dist
      |
      |-- image
      |    |__ 001-de229472.jpg
      |
      |__ index-5c5d3df7.css
      
<!-- index-5c5d3df7.css 内容不变-->

.bg {
    background-image: url('image/001-de229472.jpg');
}

这时 index-5c5d3df7.css 中对图片的引用地址是正确的

2.2 解决方法

配置 ExtractTextPlugin.extract() 中的 publicPath 选项,该选项要根据
new ExtractTextPlugin() 中是否对 CSS 设置了存放目录来配置

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

module.exports = {

    // ... 省略其他配置项
    
    module: {
        rules: [
            {
                // 用正则去匹配要用该 loader 转换的 CSS 文件
                test: /\.css$/,
                use: ExtractTextPlugin.extract({
                    // 使用 css-loader 对 css 进行压缩
                    use: [
                        {
                            loader: 'css-loader',
                            options: {
                                minimize: true
                            }
                        }
                    ],
                    publicPath: '../'
                })
            }
        ]
    },
    plugins: [
        new ExtractTextPlugin({
          // 从 js 文件中提取出来的 css 文件的名称,前面可加存放路径
          filename: `css/[name]-[contenthash:8].css`,
        })
    ]
}

为什么 publicPath 要用设置为 ../ ,因为 webpack 生成的 css ,默认位于项目打包构建后的输出目录下,这时图片的引用地址没有问题。

但是我们在 plugin 配置项 new ExtractTextPlugin({filename: 'css/[name]_[contenthash:8].css'}) 中,设置了生成的 CSS 文件存放在 css 目录下,相当于比原先的位置更深了一层,所以需要修正 CSS 文件中的引用路径往上再查找一层。

如果设置生成的 CSS 目录为 css/index/[name]_[contenthash:8].css,则 publicPath 要设置为 ../../,相当于需要往上再查找两层。

Webpack 学习第六步

1、优化输出的代码

目标:

  • 压缩输出的 JSCSSHTML
1.1 CSSHTML 的压缩

之前在我们的配置中,我们已经对 CSSHTML 进行了压缩
使用了 css-loaderhtml-webpack-plugin 插件,配置如下

module.exports = {

    // ... 省略其他配置项
    
    // 模块:例如转换 es6, less, 图片转换等...
    module: {
        rules:[
        
            // ...省略其他配置项
            
            {
                // 用正则去匹配要用该 loader 转换的 CSS 文件
                test: /\.css$/,
                use: ExtractTextPlugin.extract({    // 从 js 文件中提取 css 
                
                    // 使用 css-loader 对 css 进行压缩
                    use: [
                        {
                            loader: 'css-loader',
                            options: {
                                minimize: true
                            }
                        }
                    ]
                })
            }
        ]
    }
    plugins: [
    
        // ...省略其他配置项
        
        new HtmlWebpackPlugin({
        
            // ...省略其他配置项
            
            // 对生成的 HTML 进行压缩配置
            minify: {
                removeComments : true,      //去掉注释
                collapseWhitespace : true   //去掉空行
            }
        })
    ]
}

其中 css-loader 内部使用的是 cssnano 来对 css 进行压缩,所以 cssnano 支持的压缩选项都可以在 css-loader 中进行配置,一般不进行配置,使用默认配置。

参考资料:

cssnano 官方文档
cssnano 更新日志

注意:
    这里使用的 css-loader 版本是 v0.28.8,其内部使用的 cssnano 版本是 v3.10.0,
    目前 cssnano 版本已经升级到了 v4.0.0,官方文档也是 v4.0.0 了,  
    如果要查看 v3.10.0 的信息应该对比 cssnano 的更新日志来看。
1.3 压缩 JS

使用 Webpack 内置的 uglifyjs-webpack-plugin 插件来压缩 JS,配置如下

参考资料(注意插件的版本号):

uglifyjs-webpack-plugin GitHub 地址
UglifyJS2 配置选项

const uglifyjs = require('uglifyjs-webpack-plugin');

module.exports = {

    // ... 省略其他配置项
    
    plugins: [
    
        // ...省略其他配置项
        
        new uglifyjs({
        
            // 相关配置参数与使用的 uglifyjs-webpack-plugin 版本有关
            
            // 生成 sourceMap,配合 devtool: 'sourceMap' 使用
            sourceMap: true,
            compress: {
                // 去掉 console 打印信息
                drop_console: true
            },
            beautify: false      // 格式化输出
        })
    ]
}
注意:
    我这里使用的 Webpack 3.6.0 中的 uglifyjs-webpack-plugin 版本是 v0.4.6,
    其中 uglifyjs-webpack-plugin 插件使用的是 UglifyJS2 版本是 v2.8.29
    配置 uglifyjs-webpack-plugin 参数时要查看对应版本的官方文档
    (一般去官方 GitHub 仓库查看对应的版本即可)

Ubuntu 安装 shadowsocksR 客户端

Ubuntu 安装 shadowsocksR 客户端

快捷方法

1、下载已构建好的 deb

electron-ssr releases 下载 electron-ssr_0.2.3_amd64.deb 安装包

$ sudo dpkg -i electron-ssr_0.2.3_amd64.deb 
2、Chromium 安装 SwitchyOmega_Chromium

GitHub 官方仓库的 release 地址 下载插件

安装后,再设置 SwitchyOmega 为系统代理

最后,设置一下 SwitchyOmega 代理服务器

代理服务器地址: 127.0.0.1 -- 本地启动的 electron-ssr 使用的地址 
代理端口: 1080 -- electron-ssr 本地监听的端口

以上参数可在 electron-ssr 中配置,并且 SwitchyOmega 参数要做对应的更改
3、electron-ssr 添加代理服务器节点

ssr 免费订阅地址

复杂方法一

参考资料:
在linux环境安装shadowsocksR客户端

下载 ssr 脚本文件
wget https://github.com/the0demiurge/CharlesScripts/blob/master/charles/bin/ssr

ssr 是一个 shell 脚本

执行 ssr 脚本
$ sudo mv ssr /usr/local/bin
$ sudo chmod 766 /usr/local/bin/ssr
$ ssr install
$ ssr config

相关说明

# 作者:老徐
# SSR免费分享网站(所有帐号均来源于网上别人的分享):http://ss.pythonic.life
# 源代码主页:https://github.com/the0demiurge
# 访问https://github.com/the0demiurge/CharlesScripts/blob/master/charles/bin/ssr获取本脚本的最新版
# 使用方法:把该脚本放到$PATH里面并加入可执行权限就行(比如说放到/usr/local/bin)
# 首次使用输入ssr install后安装时会自动安装到 $HOME/.local/share/shadowsocksr
# 输入ssr config进行配置,输入JSON格式的配置文件
# 输入ssr uninstall卸载
# 输入ssr help 展示帮助信息
设置开机自启动
sudo vi /etc/rc.local
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

sudo ssr start
exit 0
客户端配置

配置文件为 config.json

{              
    "server": "jp02.fss.fun",
    "server_ipv6": "::",
    "server_port": 12853,
    "local_address": "127.0.0.1",
    "local_port": 1080,
    "password": "12064277",
    "group": "Charles Xu",
    "method": "aes-256-cfb",

    "protocol": "auth_aes128_md5",
    "protocol_param": "",
    "obfs": "tls1.2_ticket_auth_compatible",
    "obfs_param": "",
    "speed_limit_per_con": 0,
    "speed_limit_per_user": 0,

    "additional_ports" : {}, // only works under multi-user mode
    "additional_ports_only" : false, // only works under multi-user mode
    "timeout": 120,
    "udp_timeout": 60,
    "dns_ipv6": false,
    "connect_verbose_info": 0,
    "redirect": "",
    "fast_open": false
}

配置信息参考:
Configuration via Config File

ssr 免费订阅

复杂方法二

安装 electron-ssr 源码并编译
$ git clone https://github.com/erguotou520/electron-ssr

# or npm install or cnpm install
$ yarn

# 打包构建
$ npm run build

构建好后,在 build 目录会生成 deb 的包,安装即可

遇到的问题:
编译报错,构建任务里有几个打包,而打包依赖于系统上安装的打包命令

Webpack 3.x 学习-第二天

Webpack 学习第三步

1、处理 css 文件

需要用到 css-loaderstyle-loader 加载器

参考资料:

css-loader GitHub 地址
style-loader GitHub 地址

安装 css-loaderstyle-loader

cnpm i -D style-loader css-loader

配置 webpack.config.js 中的 module 项中的 rules

module.exports = {

    // ... 省略其他配置项
    
    // 模块:例如转换 es6, less, 图片转换等...
    module: {
        rules:[
            {
                // 用正则去匹配要用该 loader 转换的 CSS 文件
                test: /\.css$/,
                use: ['style-loader', 'css-loader?minimize']
            }
        ]
    }
}

如上配置告诉 Webpack 在遇到以 .css 结尾的文件时先使用 css-loader 读取 CSS 文件,再交给 style-loaderCSS 内容注入到 JavaScript 里。 在配置 Loader 时需要注意的是:

  • use 属性的值需要是一个由 Loader 名称组成的数组,Loader 的执行顺序是由后到前的;
  • 每一个 Loader 都可以通过 URL querystring 的方式传入参数,例如 css-loader?minimize 中的 minimize 告诉 css-loader 要开启 CSS 压缩

style-loader 的工作原理大概是把 CSS 内容用 JavaScript 里的字符串存储起来, 在网页执行 JavaScript 时通过 DOM 操作动态地往 HTML head 标签里插入 HTML style 标签。

2、提取 CSS 文件

在上面重新执行构建后,你会发现 index.js 文件被更新了,里面注入了在 index.css 中的 CSS,而不是额外生成一个 CSS 文件,这里需要用到 Webpack 的插件,即 extract-text-webpack-plugin

参考资料:

extract-text-webpack-plugin GitHub 地址

安装 extract-text-webpack-plugin 插件

cnpm i -D extract-text-webpack-plugin

webpack 相关配置

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

module.exports = {

    // ... 省略其他配置项
    
    module: {
        rules: [
            {
                // 用正则去匹配要用该 loader 转换的 CSS 文件
                test: /\.css$/,
                use: ExtractTextPlugin.extract({
                    // 转换 .css 文件需要使用的 Loader
                    use: ['css-loader?minimize']
                })
            }
        ]
    },
    plugins: [
        new ExtractTextPlugin({
          // 从 js 文件中提取出来的 css 文件的名称,前面可加存放路径
          filename: `css/[name]_[contenthash:8].css`,
        })
    ]
}

学习 Webpack 第四步

1、实现自动化,提高开发效率

1.1 自动化需求:

1、提供 HTTP 服务 而不是使用本地文件预览;
2、监听文件的变化并自动刷新网页,做到实时预览;
3、支持 Source Map ,以方便调试;

参考资料

webpack-dev-server GitHub 地址
DevServer 官方文档

1.2 webpack-dev-server 的安装及使用

安装

cnpm i -D webpack-dev-server

首先在 package.json 中配置 npm script 脚本命令来启动 webpack-dev-server

"scripts": {
    "dev": "webpack-dev-server"
}

然后在 Webpack 配置文件的 devServer 配置项中对 webpack-dev-server 进行配置

const webpack = require('webpack');

module.exports = {

    // ... 省略其他配置项
    
    plugins: [
        new webpack.HotModuleReplacementPlugin()    // 开启模块热替换需要使用的插件
    ],
    // 配置 webpack 开发本地服务器功能
    devServer: {
        // 服务器的IP地址,默认为 localhost
        host:'localhost',
        // 服务端压缩是否开启
        compress:true,
        // 配置服务端口号,默认为 8080
        port:8000,
        // 使用默认浏览器自动打开网页
        open: true,
        // 开启模块热替换 (不刷新网页进行更新)
        hot: true
    }
}

webpack-dev-server 会把 Webpack 构建出的文件保存在内存中,在要访问输出的文件时,必须通过 HTTP 服务访问

这里开启模块热替换有两种方法:

方法一:通过 'webpack-dev-server' 命令行选项 '--hot' 来指定开启模块热替换

// 在 package.json 文件中配置 npm script 脚本命令
"scripts": {
    "dev": "webpack-dev-server --hot"
}


方法二:在配置文件的 'devServer' 选项中开启模块热替换时,需要在 'plugins' 配置项中加入  
'Webpack' 自带 'HotModuleReplacementPlugin' 插件,所以需要先引入 `webpack`,代码如下:  

const webpack = require('webpack');

module.exports = {

    // ... 省略其他配置项
    
    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ],
    // 配置 webpack 开发本地服务器功能
    devServer: {
        // 开启模块热替换 (不刷新网页进行更新)
        hot: true
        
        // ... 省略其他配置项
    }
}

如果使用方法二,没有引入 HotModuleReplacementPlugin 插件,网页控制台会显示出错信息: Uncaught Error: [HMR] Hot Module Replacement is disabled.

出错相关参考资料

GitHub 上相关的 issue
webpack-dev-server 官方文档相关说明

1.3、遇到的问题

1.2.1、构建失败,报错信息为:Cannot use [chunkhash] for chunk in 'js/[name]-[chunkhash:8].js' (use [hash] instead)

这里注意,如果开启了模块热替换,Webpack 构建打包不能使用 chunkhash 来给输出的文件命名,而应该使用 hash,否则构建会失败

module.exports = {
    // 入口文件的配置项
    entry: {
        index: './src/js/index.js'
    },
    // 出口文件的配置项
    output: {
        path: path.resolve(__dirname, 'dist'),
        // 当开启模块热替换时,使用 hash
        filename: 'js/[name]-[hash:8].js'
    }
    
     // ... 省略其他配置项
}

1.2.2、修改 css 文件后,页面并没有变化

原因是,使用了 ExtractTextPlugin 插件来提取 css 到一个独立的样式文件,而 ExtractTextPlugin 不支持模块热替换,因此不要单独提取 css

module.exports = {

    // ... 省略其他配置项
 
    module: {
        rules:[
            {
                // 用正则去匹配要用该 loader 转换的 CSS 文件
                test: /\.css$/,
                use: ExtractTextPlugin.extract({    // 从 js 文件中提取 css 
                    // 转换 .css 文件需要使用的 Loader
                    use: ['css-loader?minimize']
                })
            }
        ]
    }
}

改为

module.exports = {

    // ... 省略其他配置项
 
    module: {
        rules:[
            {
                // 用正则去匹配要用该 loader 转换的 CSS 文件
                test: /\.css$/,
                use:  ['style-loader', 'css-loader?minimize']
            }
        ]
    }
}

这时修改 css 文件会触发模块热替换, 因为 style-loader 会注入用于接受 CSS 的代码。

1.4 Source Map 配置

参考资料

webpack 官方文档 使用 Source Map
Devtool 官方文档

有两种方式

方法一:在 Webpack 配置文件中配置 devtool 选项即可

devtool 配置可选项很多,具体可参考上面的 Devtool 官方文档

常用的有如下四个:

  • source-map : 在一个单独文件中产生一个完整且功能完全的文件。这个文件具有最好的 source map,但是它会减慢打包速度;
  • cheap-module-source-map : 在一个单独的文件中产生一个不带列映射的 map ,不带列映射提高了打包速度,但是也使得浏览器开发者工具只能对应到具体的行,不能对应到具体的列;
  • eval-source-map : 使用 eval 打包源文件模块,在同一个文件中生产干净的完整版的 source map,但是对打包后输出的 JS 文件的执行具有性能和安全的隐患;
  • cheap-module-eval-source-map : 这是在打包文件时最快的生产 source map 的方法,生产的 source map 会和打包后的 JavaScript 文件同行显示,没有影射列;

如果大型项目可以使用 source-map,如果是中小型项目使用 eval-source-map 就完全可以

配置选项如下

module.exports = {

    // ... 省略其他配置项
    
    devtool: 'source-map'
}

方法二:在 webpack-dev-server 命令行选项加入 --devtool source-map 参数来指定以 source-map 的方式生成 Source Map--devtool 可选参数同方法一)

"scripts": {
    "dev": "webpack-dev-server --devtool source-map"
}

2、分离开发环境和生产环境配置

在上面使用 webpack-dev-server 开启模块热替换,提高开发效率的同时,需要对 Webpack 配置文件做相应的修改,其中一些修改和代码优化设置不兼容,这就迫使我们做出改变,针对 开发环境生产环境 ,分别使用不同的配置文件

开发环境Webpack 配置文件为 webpack.config.dev.js
生产环境Webpack 配置文件为 webpack.config.prod.js

package.json 中的构建脚本改为

"scripts": {
    "dev": "webpack-dev-server --config webpack.config.dev.js",
    "build": "webpack --config webpack.config.prod.js"
}

webpack.config.dev.js 配置文件内容如下

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');

module.exports = {
    // 入口文件的配置项
    entry: {
        index: './src/js/index.js'
    },
    // 出口文件的配置项
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'js/[name]-[hash:8].js'
    },
    // 模块:例如转换 es6, less, 图片转换等...
    module: {
        rules:[
            {
                // 用正则去匹配要用该 loader 转换的 CSS 文件
                test: /\.css$/,
                use:  ['style-loader', 'css-loader?minimize']
            }
        ]
    },
    // 插件,用于生产模板等各项功能
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new HtmlWebpackPlugin({
            title: 'learn webpack',         // 标题
            template: './src/index.html',   // html 模板
            filename: 'index.html',         // 生成的 HTML 名称
            inject: 'true',                 // 引入生成的 js 文件,false 则不引入
            minify: {
                removeComments : true,      //去掉注释
                collapseWhitespace : true   //去掉空行
            }
        })
    ],
    // 配置 Source Maps
    devtool: 'source-map',
    // 配置 webpack 开发本地服务器功能
    devServer: {
        // 服务器的IP地址,默认为 localhost
        host:'localhost',
        // 服务端压缩是否开启
        compress:true,
        // 配置服务端口号,默认为 8080
        port:8000,
        // 使用默认浏览器自动打开网页
        open: true,
        // 开启模块热替换 (不刷新网页进行更新)
        hot: true
    }
}

webpack.config.prod.js 配置文件内容如下

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
    // 入口文件的配置项
    entry: {
        index: './src/js/index.js',
        // list: './src/js/list.js'
    },
    // 出口文件的配置项
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'js/[name]-[chunkhash:8].js'
    },
    // 模块:例如转换 es6, less, 图片转换等...
    module: {
        rules:[
            {
                // 用正则去匹配要用该 loader 转换的 CSS 文件
                test: /\.css$/,
                use: ExtractTextPlugin.extract({    // 从 js 文件中提取 css 
                    // 转换 .css 文件需要使用的 Loader
                    use: ['css-loader?minimize']
                })
            }
        ]
    },
    // 插件,用于生产模板等各项功能
    plugins: [
        new CleanWebpackPlugin(['dist']),   //构建前,删除 dist 目录
        new HtmlWebpackPlugin({
            title: 'learn webpack',         // 标题
            template: './src/index.html',   // html 模板
            filename: 'index.html',         // 生成的 HTML 名称
            inject: 'true',                 // 引入生成的 js 文件,false 则不引入
            minify: {
                removeComments : true,      //去掉注释
                collapseWhitespace : true   //去掉空行
            }
        }),
        new ExtractTextPlugin({
            // 从 .js 文件中提取出来的 .css 文件的名称
            filename: 'css/[name]_[contenthash:8].css'
        })
    ]
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.